♻️(backend) refactor post hackathon to a first working version

This project was copied and hacked to make a POC in a 2-day hackathon.
We need to clean and refactor things in order to get a first version
of the product we want.
This commit is contained in:
Samuel Paccoud - DINUM
2024-02-09 19:32:12 +01:00
committed by Samuel Paccoud
parent 0a3e26486e
commit 0f9327a1de
41 changed files with 2259 additions and 2567 deletions

View File

@@ -0,0 +1,47 @@
"""
Tests for Templates API endpoint in publish's core app: create
"""
import pytest
from rest_framework.test import APIClient
from core import factories
from core.models import Template
from core.tests.utils import OIDCToken
pytestmark = pytest.mark.django_db
def test_api_templates_create_anonymous():
"""Anonymous users should not be allowed to create templates."""
response = APIClient().post(
"/api/v1.0/templates/",
{
"title": "my template",
},
)
assert response.status_code == 401
assert not Template.objects.exists()
def test_api_templates_create_authenticated():
"""
Authenticated users should be able to create templates and should automatically be declared
as the owner of the newly created template.
"""
user = factories.UserFactory()
jwt_token = OIDCToken.for_user(user)
response = APIClient().post(
"/api/v1.0/templates/",
{
"title": "my template",
},
format="json",
HTTP_AUTHORIZATION=f"Bearer {jwt_token}",
)
assert response.status_code == 201
template = Template.objects.get()
assert template.title == "my template"
assert template.accesses.filter(role="owner", user=user).exists()

View File

@@ -0,0 +1,84 @@
"""
Tests for Templates API endpoint in publish's core app: delete
"""
import random
import pytest
from rest_framework.test import APIClient
from core import factories, models
from core.tests.utils import OIDCToken
pytestmark = pytest.mark.django_db
def test_api_templates_delete_anonymous():
"""Anonymous users should not be allowed to destroy a template."""
template = factories.TemplateFactory()
response = APIClient().delete(
f"/api/v1.0/templates/{template.id!s}/",
)
assert response.status_code == 401
assert models.Template.objects.count() == 1
def test_api_templates_delete_authenticated_unrelated():
"""
Authenticated users should not be allowed to delete a template to which they are not
related.
"""
user = factories.UserFactory()
jwt_token = OIDCToken.for_user(user)
is_public = random.choice([True, False])
template = factories.TemplateFactory(is_public=is_public)
response = APIClient().delete(
f"/api/v1.0/templates/{template.id!s}/",
HTTP_AUTHORIZATION=f"Bearer {jwt_token}",
)
assert response.status_code == 403 if is_public else 404
assert models.Template.objects.count() == 1
@pytest.mark.parametrize("role", ["member", "administrator"])
def test_api_templates_delete_authenticated_member(role):
"""
Authenticated users should not be allowed to delete a template for which they are
only a member.
"""
user = factories.UserFactory()
jwt_token = OIDCToken.for_user(user)
template = factories.TemplateFactory(users=[(user, role)])
response = APIClient().delete(
f"/api/v1.0/templates/{template.id}/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}"
)
assert response.status_code == 403
assert response.json() == {
"detail": "You do not have permission to perform this action."
}
assert models.Template.objects.count() == 1
def test_api_templates_delete_authenticated_owner():
"""
Authenticated users should be able to delete a template for which they are directly
owner.
"""
user = factories.UserFactory()
jwt_token = OIDCToken.for_user(user)
template = factories.TemplateFactory(users=[(user, "owner")])
response = APIClient().delete(
f"/api/v1.0/templates/{template.id}/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}"
)
assert response.status_code == 204
assert models.Template.objects.exists() is False

View File

@@ -0,0 +1,107 @@
"""
Test users API endpoints in the publish core app.
"""
import pytest
from rest_framework.test import APIClient
from core import factories
from core.tests.utils import OIDCToken
pytestmark = pytest.mark.django_db
def test_api_templates_generate_document_anonymous_public():
"""Anonymous users can generate pdf document with public templates."""
template = factories.TemplateFactory(is_public=True)
data = {
"body": "# Test markdown body",
}
response = APIClient().post(
f"/api/v1.0/templates/{template.id!s}/generate-document/",
data,
format="json",
)
assert response.status_code == 200
assert response.headers["content-type"] == "application/pdf"
def test_api_templates_generate_document_anonymous_not_public():
"""
Anonymous users should not be allowed to generate pdf document with templates
that are not marked as public.
"""
template = factories.TemplateFactory(is_public=False)
data = {
"body": "# Test markdown body",
}
response = APIClient().post(
f"/api/v1.0/templates/{template.id!s}/generate-document/",
data,
format="json",
)
assert response.status_code == 404
assert response.json() == {"detail": "Not found."}
def test_api_templates_generate_document_authenticated_public():
"""Authenticated users can generate pdf document with public templates."""
user = factories.UserFactory()
jwt_token = OIDCToken.for_user(user)
template = factories.TemplateFactory(is_public=True)
data = {"body": "# Test markdown body"}
response = APIClient().post(
f"/api/v1.0/templates/{template.id!s}/generate-document/",
data,
format="json",
HTTP_AUTHORIZATION=f"Bearer {jwt_token}",
)
assert response.status_code == 200
assert response.headers["content-type"] == "application/pdf"
def test_api_templates_generate_document_authenticated_not_public():
"""
Authenticated users should not be allowed to generate pdf document with templates
that are not marked as public.
"""
user = factories.UserFactory()
jwt_token = OIDCToken.for_user(user)
template = factories.TemplateFactory(is_public=False)
data = {"body": "# Test markdown body"}
response = APIClient().post(
f"/api/v1.0/templates/{template.id!s}/generate-document/",
data,
format="json",
HTTP_AUTHORIZATION=f"Bearer {jwt_token}",
)
assert response.status_code == 404
assert response.json() == {"detail": "Not found."}
def test_api_templates_generate_document_related():
"""Users related to a template can generate pdf document."""
user = factories.UserFactory()
jwt_token = OIDCToken.for_user(user)
access = factories.TemplateAccessFactory(user=user)
data = {"body": "# Test markdown body"}
response = APIClient().post(
f"/api/v1.0/templates/{access.template.id!s}/generate-document/",
data,
format="json",
HTTP_AUTHORIZATION=f"Bearer {jwt_token}",
)
assert response.status_code == 200
assert response.headers["content-type"] == "application/pdf"

View File

@@ -0,0 +1,124 @@
"""
Tests for Templates API endpoint in publish's core app: list
"""
from unittest import mock
import pytest
from rest_framework.pagination import PageNumberPagination
from rest_framework.status import HTTP_200_OK
from rest_framework.test import APIClient
from core import factories
from core.tests.utils import OIDCToken
pytestmark = pytest.mark.django_db
def test_api_templates_list_anonymous():
"""Anonymous users should only be able to list public templates."""
factories.TemplateFactory.create_batch(2, is_public=False)
templates = factories.TemplateFactory.create_batch(2, is_public=True)
expected_ids = {str(template.id) for template in templates}
response = APIClient().get("/api/v1.0/templates/")
assert response.status_code == HTTP_200_OK
results = response.json()["results"]
assert len(results) == 2
results_id = {result["id"] for result in results}
assert expected_ids == results_id
def test_api_templates_list_authenticated():
"""
Authenticated users should be able to list templates they are
an owner/administrator/member of.
"""
user = factories.UserFactory()
jwt_token = OIDCToken.for_user(user)
related_templates = [
access.template
for access in factories.TemplateAccessFactory.create_batch(5, user=user)
]
public_templates = factories.TemplateFactory.create_batch(2, is_public=True)
factories.TemplateFactory.create_batch(2, is_public=False)
expected_ids = {
str(template.id) for template in related_templates + public_templates
}
response = APIClient().get(
"/api/v1.0/templates/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}"
)
assert response.status_code == HTTP_200_OK
results = response.json()["results"]
assert len(results) == 7
results_id = {result["id"] for result in results}
assert expected_ids == results_id
@mock.patch.object(PageNumberPagination, "get_page_size", return_value=2)
def test_api_templates_list_pagination(
_mock_page_size,
):
"""Pagination should work as expected."""
user = factories.UserFactory()
jwt_token = OIDCToken.for_user(user)
template_ids = [
str(access.template.id)
for access in factories.TemplateAccessFactory.create_batch(3, user=user)
]
# Get page 1
response = APIClient().get(
"/api/v1.0/templates/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}"
)
assert response.status_code == HTTP_200_OK
content = response.json()
assert content["count"] == 3
assert content["next"] == "http://testserver/api/v1.0/templates/?page=2"
assert content["previous"] is None
assert len(content["results"]) == 2
for item in content["results"]:
template_ids.remove(item["id"])
# Get page 2
response = APIClient().get(
"/api/v1.0/templates/?page=2", HTTP_AUTHORIZATION=f"Bearer {jwt_token}"
)
assert response.status_code == HTTP_200_OK
content = response.json()
assert content["count"] == 3
assert content["next"] is None
assert content["previous"] == "http://testserver/api/v1.0/templates/"
assert len(content["results"]) == 1
template_ids.remove(content["results"][0]["id"])
assert template_ids == []
def test_api_templates_list_authenticated_distinct():
"""A template with several related users should only be listed once."""
user = factories.UserFactory()
jwt_token = OIDCToken.for_user(user)
other_user = factories.UserFactory()
template = factories.TemplateFactory(users=[user, other_user], is_public=True)
response = APIClient().get(
"/api/v1.0/templates/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}"
)
assert response.status_code == HTTP_200_OK
content = response.json()
assert len(content["results"]) == 1
assert content["results"][0]["id"] == str(template.id)

View File

@@ -0,0 +1,130 @@
"""
Tests for Templates API endpoint in publish's core app: retrieve
"""
import pytest
from rest_framework.test import APIClient
from core import factories
from core.tests.utils import OIDCToken
pytestmark = pytest.mark.django_db
def test_api_templates_retrieve_anonymous_public():
"""Anonymous users should be allowed to retrieve public templates."""
template = factories.TemplateFactory(is_public=True)
response = APIClient().get(f"/api/v1.0/templates/{template.id!s}/")
assert response.status_code == 200
assert response.json() == {
"id": str(template.id),
"abilities": {
"destroy": False,
"generate_document": True,
"manage_accesses": False,
"retrieve": True,
"update": False,
},
"accesses": [],
"title": template.title,
}
def test_api_templates_retrieve_anonymous_not_public():
"""Anonymous users should not be able to retrieve a template that is not public."""
template = factories.TemplateFactory(is_public=False)
response = APIClient().get(f"/api/v1.0/templates/{template.id!s}/")
assert response.status_code == 404
assert response.json() == {"detail": "Not found."}
def test_api_templates_retrieve_authenticated_unrelated_public():
"""
Authenticated users should be able to retrieve a public template to which they are
not related.
"""
user = factories.UserFactory()
jwt_token = OIDCToken.for_user(user)
template = factories.TemplateFactory(is_public=True)
response = APIClient().get(
f"/api/v1.0/templates/{template.id!s}/",
HTTP_AUTHORIZATION=f"Bearer {jwt_token}",
)
assert response.status_code == 200
assert response.json() == {
"id": str(template.id),
"abilities": {
"destroy": False,
"generate_document": True,
"manage_accesses": False,
"retrieve": True,
"update": False,
},
"accesses": [],
"title": template.title,
}
def test_api_templates_retrieve_authenticated_unrelated_not_public():
"""
Authenticated users should not be allowed to retrieve a template that is not public and
to which they are not related.
"""
user = factories.UserFactory()
jwt_token = OIDCToken.for_user(user)
template = factories.TemplateFactory(is_public=False)
response = APIClient().get(
f"/api/v1.0/templates/{template.id!s}/",
HTTP_AUTHORIZATION=f"Bearer {jwt_token}",
)
assert response.status_code == 404
assert response.json() == {"detail": "Not found."}
def test_api_templates_retrieve_authenticated_related():
"""
Authenticated users should be allowed to retrieve a template to which they
are related whatever the role.
"""
user = factories.UserFactory()
jwt_token = OIDCToken.for_user(user)
template = factories.TemplateFactory()
access1 = factories.TemplateAccessFactory(template=template, user=user)
access2 = factories.TemplateAccessFactory(template=template)
response = APIClient().get(
f"/api/v1.0/templates/{template.id!s}/",
HTTP_AUTHORIZATION=f"Bearer {jwt_token}",
)
assert response.status_code == 200
content = response.json()
assert sorted(content.pop("accesses"), key=lambda x: x["user"]) == sorted(
[
{
"id": str(access1.id),
"user": str(user.id),
"role": access1.role,
"abilities": access1.get_abilities(user),
},
{
"id": str(access2.id),
"user": str(access2.user.id),
"role": access2.role,
"abilities": access2.get_abilities(user),
},
],
key=lambda x: x["user"],
)
assert response.json() == {
"id": str(template.id),
"title": template.title,
"abilities": template.get_abilities(user),
}

View File

@@ -0,0 +1,154 @@
"""
Tests for Templates API endpoint in publish's core app: update
"""
import random
import pytest
from rest_framework.test import APIClient
from core import factories
from core.api import serializers
from core.tests.utils import OIDCToken
pytestmark = pytest.mark.django_db
def test_api_templates_update_anonymous():
"""Anonymous users should not be allowed to update a template."""
template = factories.TemplateFactory()
old_template_values = serializers.TemplateSerializer(instance=template).data
new_template_values = serializers.TemplateSerializer(
instance=factories.TemplateFactory()
).data
response = APIClient().put(
f"/api/v1.0/templates/{template.id!s}/",
new_template_values,
format="json",
)
assert response.status_code == 401
assert response.json() == {
"detail": "Authentication credentials were not provided."
}
template.refresh_from_db()
template_values = serializers.TemplateSerializer(instance=template).data
assert template_values == old_template_values
def test_api_templates_update_authenticated_unrelated():
"""
Authenticated users should not be allowed to update a template to which they are not related.
"""
user = factories.UserFactory()
jwt_token = OIDCToken.for_user(user)
template = factories.TemplateFactory(is_public=False)
old_template_values = serializers.TemplateSerializer(instance=template).data
new_template_values = serializers.TemplateSerializer(
instance=factories.TemplateFactory()
).data
response = APIClient().put(
f"/api/v1.0/templates/{template.id!s}/",
new_template_values,
format="json",
HTTP_AUTHORIZATION=f"Bearer {jwt_token}",
)
assert response.status_code == 404
assert response.json() == {"detail": "Not found."}
template.refresh_from_db()
template_values = serializers.TemplateSerializer(instance=template).data
assert template_values == old_template_values
def test_api_templates_update_authenticated_members():
"""
Users who are members of a template but not administrators should
not be allowed to update it.
"""
user = factories.UserFactory()
jwt_token = OIDCToken.for_user(user)
template = factories.TemplateFactory(users=[(user, "member")])
old_template_values = serializers.TemplateSerializer(instance=template).data
new_template_values = serializers.TemplateSerializer(
instance=factories.TemplateFactory()
).data
response = APIClient().put(
f"/api/v1.0/templates/{template.id!s}/",
new_template_values,
format="json",
HTTP_AUTHORIZATION=f"Bearer {jwt_token}",
)
assert response.status_code == 403
assert response.json() == {
"detail": "You do not have permission to perform this action."
}
template.refresh_from_db()
template_values = serializers.TemplateSerializer(instance=template).data
assert template_values == old_template_values
@pytest.mark.parametrize("role", ["administrator", "owner"])
def test_api_templates_update_authenticated_administrators(role):
"""Administrators of a template should be allowed to update it."""
user = factories.UserFactory()
jwt_token = OIDCToken.for_user(user)
template = factories.TemplateFactory(users=[(user, role)])
old_template_values = serializers.TemplateSerializer(instance=template).data
new_template_values = serializers.TemplateSerializer(
instance=factories.TemplateFactory()
).data
response = APIClient().put(
f"/api/v1.0/templates/{template.id!s}/",
new_template_values,
format="json",
HTTP_AUTHORIZATION=f"Bearer {jwt_token}",
)
assert response.status_code == 200
template.refresh_from_db()
template_values = serializers.TemplateSerializer(instance=template).data
for key, value in template_values.items():
if key in ["id", "accesses"]:
assert value == old_template_values[key]
else:
assert value == new_template_values[key]
def test_api_templates_update_administrator_or_owner_of_another():
"""
Being administrator or owner of a template should not grant authorization to update
another template.
"""
user = factories.UserFactory()
jwt_token = OIDCToken.for_user(user)
factories.TemplateFactory(users=[(user, random.choice(["administrator", "owner"]))])
is_public = random.choice([True, False])
template = factories.TemplateFactory(title="Old title", is_public=is_public)
old_template_values = serializers.TemplateSerializer(instance=template).data
new_template_values = serializers.TemplateSerializer(
instance=factories.TemplateFactory()
).data
response = APIClient().put(
f"/api/v1.0/templates/{template.id!s}/",
new_template_values,
format="json",
HTTP_AUTHORIZATION=f"Bearer {jwt_token}",
)
assert response.status_code == 403 if is_public else 404
template.refresh_from_db()
template_values = serializers.TemplateSerializer(instance=template).data
assert template_values == old_template_values