mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-09-11 04:08:00 +00:00
* Chore(deps): Bump the utilities-minor group across 1 directory with 11 updates Bumps the utilities-minor group with 11 updates in the / directory: | Package | From | To | | --- | --- | --- | | [django-guardian](https://github.com/django-guardian/django-guardian) | `3.3.3` | `3.4.0` | | [django-treenode](https://github.com/fabiocaccamo/django-treenode) | `0.24.0` | `0.25.0` | | [drf-spectacular-sidecar](https://github.com/tfranzel/drf-spectacular-sidecar) | `2026.7.1` | `2026.8.1` | | [imap-tools](https://github.com/ikvk/imap_tools) | `1.14.0` | `1.15.0` | | [ocrmypdf](https://github.com/ocrmypdf/OCRmyPDF) | `17.10.0` | `17.11.0` | | [prek](https://github.com/j178/prek) | `0.4.11` | `0.5.0` | | [faker](https://github.com/joke2k/faker) | `40.36.0` | `40.37.0` | | [pytest-django](https://github.com/pytest-dev/pytest-django) | `4.12.0` | `4.14.0` | | [pytest-rerunfailures](https://github.com/pytest-dev/pytest-rerunfailures) | `16.4` | `16.6` | | [time-machine](https://github.com/adamchainz/time-machine) | `3.2.0` | `3.5.0` | | [types-pygments](https://github.com/python/typeshed) | `2.20.0.20260408` | `2.21.0.20260819` | Updates `django-guardian` from 3.3.3 to 3.4.0 - [Release notes](https://github.com/django-guardian/django-guardian/releases) - [Commits](https://github.com/django-guardian/django-guardian/compare/3.3.3...3.4.0) Updates `django-treenode` from 0.24.0 to 0.25.0 - [Release notes](https://github.com/fabiocaccamo/django-treenode/releases) - [Changelog](https://github.com/fabiocaccamo/django-treenode/blob/main/CHANGELOG.md) - [Commits](https://github.com/fabiocaccamo/django-treenode/compare/0.24.0...0.25.0) Updates `drf-spectacular-sidecar` from 2026.7.1 to 2026.8.1 - [Commits](https://github.com/tfranzel/drf-spectacular-sidecar/compare/2026.7.1...2026.8.1) Updates `imap-tools` from 1.14.0 to 1.15.0 - [Release notes](https://github.com/ikvk/imap_tools/releases) - [Changelog](https://github.com/ikvk/imap_tools/blob/master/docs/release_notes.rst) - [Commits](https://github.com/ikvk/imap_tools/compare/v1.14.0...v1.15.0) Updates `ocrmypdf` from 17.10.0 to 17.11.0 - [Release notes](https://github.com/ocrmypdf/OCRmyPDF/releases) - [Commits](https://github.com/ocrmypdf/OCRmyPDF/compare/v17.10.0...v17.11.0) Updates `prek` from 0.4.11 to 0.5.0 - [Release notes](https://github.com/j178/prek/releases) - [Changelog](https://github.com/j178/prek/blob/master/CHANGELOG.md) - [Commits](https://github.com/j178/prek/compare/v0.4.11...v0.5.0) Updates `faker` from 40.36.0 to 40.37.0 - [Release notes](https://github.com/joke2k/faker/releases) - [Changelog](https://github.com/joke2k/faker/blob/master/CHANGELOG.md) - [Commits](https://github.com/joke2k/faker/compare/v40.36.0...v40.37.0) Updates `pytest-django` from 4.12.0 to 4.14.0 - [Release notes](https://github.com/pytest-dev/pytest-django/releases) - [Changelog](https://github.com/pytest-dev/pytest-django/blob/main/docs/changelog.rst) - [Commits](https://github.com/pytest-dev/pytest-django/compare/v4.12.0...v4.14.0) Updates `pytest-rerunfailures` from 16.4 to 16.6 - [Changelog](https://github.com/pytest-dev/pytest-rerunfailures/blob/master/CHANGES.rst) - [Commits](https://github.com/pytest-dev/pytest-rerunfailures/compare/16.4...16.6) Updates `time-machine` from 3.2.0 to 3.5.0 - [Changelog](https://github.com/adamchainz/time-machine/blob/main/docs/changelog.rst) - [Commits](https://github.com/adamchainz/time-machine/compare/3.2.0...3.5.0) Updates `types-pygments` from 2.20.0.20260408 to 2.21.0.20260819 - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: django-guardian dependency-version: 3.4.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: utilities-minor - dependency-name: django-treenode dependency-version: 0.25.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: utilities-minor - dependency-name: drf-spectacular-sidecar dependency-version: 2026.8.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: utilities-minor - dependency-name: faker dependency-version: 40.37.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: utilities-minor - dependency-name: imap-tools dependency-version: 1.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: utilities-minor - dependency-name: ocrmypdf dependency-version: 17.11.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: utilities-minor - dependency-name: prek dependency-version: 0.5.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: utilities-minor - dependency-name: pytest-django dependency-version: 4.14.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: utilities-minor - dependency-name: pytest-rerunfailures dependency-version: '16.6' dependency-type: direct:development update-type: version-update:semver-minor dependency-group: utilities-minor - dependency-name: time-machine dependency-version: 3.5.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: utilities-minor - dependency-name: types-pygments dependency-version: 2.21.0.20260819 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: utilities-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Handles pytest-django * Formatting --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Trenton Holmes <797416+stumpylog@users.noreply.github.com>
239 lines
8.2 KiB
Python
239 lines
8.2 KiB
Python
import logging
|
|
|
|
import pytest
|
|
from allauth.account.adapter import get_adapter
|
|
from allauth.core import context
|
|
from allauth.socialaccount.adapter import get_adapter as get_social_adapter
|
|
from django.contrib.auth.models import AnonymousUser
|
|
from django.contrib.auth.models import Group
|
|
from django.contrib.auth.models import User
|
|
from django.forms import ValidationError
|
|
from django.http import HttpRequest
|
|
from django.urls import reverse
|
|
from pytest_django.fixtures import Settings
|
|
from pytest_mock import MockerFixture
|
|
from rest_framework.authtoken.models import Token
|
|
|
|
from paperless.adapter import DrfTokenStrategy
|
|
|
|
|
|
@pytest.mark.django_db
|
|
class TestCustomAccountAdapter:
|
|
def test_is_open_for_signup(self, settings: Settings) -> None:
|
|
adapter = get_adapter()
|
|
|
|
# With no accounts, signups should be allowed
|
|
assert adapter.is_open_for_signup(None)
|
|
|
|
User.objects.create_user("testuser")
|
|
|
|
settings.ACCOUNT_ALLOW_SIGNUPS = True
|
|
assert adapter.is_open_for_signup(None)
|
|
|
|
settings.ACCOUNT_ALLOW_SIGNUPS = False
|
|
assert not adapter.is_open_for_signup(None)
|
|
|
|
def test_is_safe_url(self, settings: Settings) -> None:
|
|
request = HttpRequest()
|
|
request.get_host = lambda: "example.com"
|
|
with context.request_context(request):
|
|
adapter = get_adapter()
|
|
|
|
settings.ALLOWED_HOSTS = ["*"]
|
|
# True because request host is same
|
|
assert adapter.is_safe_url("https://example.com")
|
|
# False despite wildcard because request host is different
|
|
assert not adapter.is_safe_url("https://evil.com")
|
|
|
|
settings.ALLOWED_HOSTS = ["example.com"]
|
|
# True because request host is same
|
|
assert adapter.is_safe_url("https://example.com")
|
|
|
|
settings.ALLOWED_HOSTS = ["*", "example.com"]
|
|
# False because request host is not in allowed hosts
|
|
assert not adapter.is_safe_url("//evil.com")
|
|
|
|
def test_pre_authenticate(
|
|
self,
|
|
settings: Settings,
|
|
mocker: MockerFixture,
|
|
) -> None:
|
|
mocker.patch("allauth.core.internal.ratelimit.consume", return_value=True)
|
|
adapter = get_adapter()
|
|
request = HttpRequest()
|
|
request.get_host = lambda: "example.com"
|
|
|
|
settings.DISABLE_REGULAR_LOGIN = False
|
|
adapter.pre_authenticate(request)
|
|
|
|
settings.DISABLE_REGULAR_LOGIN = True
|
|
with pytest.raises(ValidationError):
|
|
adapter.pre_authenticate(request)
|
|
|
|
def test_get_reset_password_from_key_url(self, settings: Settings) -> None:
|
|
request = HttpRequest()
|
|
request.get_host = lambda: "foo.org"
|
|
with context.request_context(request):
|
|
adapter = get_adapter()
|
|
|
|
settings.PAPERLESS_URL = None
|
|
settings.ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https"
|
|
expected_url = f"https://foo.org{reverse('account_reset_password_from_key', kwargs={'uidb36': 'UID', 'key': 'KEY'})}"
|
|
assert adapter.get_reset_password_from_key_url("UID-KEY") == expected_url
|
|
|
|
settings.PAPERLESS_URL = "https://bar.com"
|
|
expected_url = f"https://bar.com{reverse('account_reset_password_from_key', kwargs={'uidb36': 'UID', 'key': 'KEY'})}"
|
|
assert adapter.get_reset_password_from_key_url("UID-KEY") == expected_url
|
|
|
|
def test_save_user_adds_groups(
|
|
self,
|
|
settings: Settings,
|
|
mocker: MockerFixture,
|
|
) -> None:
|
|
settings.ACCOUNT_DEFAULT_GROUPS = ["group1", "group2"]
|
|
Group.objects.create(name="group1")
|
|
user = User.objects.create_user("testuser")
|
|
adapter = get_adapter()
|
|
form = mocker.MagicMock(
|
|
cleaned_data={
|
|
"username": "testuser",
|
|
"email": "user@example.com",
|
|
},
|
|
)
|
|
|
|
user = adapter.save_user(HttpRequest(), user, form, commit=True)
|
|
|
|
assert user.groups.count() == 1
|
|
assert user.groups.filter(name="group1").exists()
|
|
assert not user.groups.filter(name="group2").exists()
|
|
|
|
def test_fresh_install_save_creates_superuser(self, mocker: MockerFixture) -> None:
|
|
adapter = get_adapter()
|
|
form = mocker.MagicMock(
|
|
cleaned_data={
|
|
"username": "testuser",
|
|
"email": "user@paperless-ngx.com",
|
|
},
|
|
)
|
|
user = adapter.save_user(HttpRequest(), User(), form, commit=True)
|
|
assert user.is_superuser
|
|
|
|
form = mocker.MagicMock(
|
|
cleaned_data={
|
|
"username": "testuser2",
|
|
"email": "user2@paperless-ngx.com",
|
|
},
|
|
)
|
|
user2 = adapter.save_user(HttpRequest(), User(), form, commit=True)
|
|
assert not user2.is_superuser
|
|
|
|
|
|
class TestCustomSocialAccountAdapter:
|
|
@pytest.mark.django_db
|
|
def test_is_open_for_signup(self, settings: Settings) -> None:
|
|
adapter = get_social_adapter()
|
|
|
|
settings.SOCIALACCOUNT_ALLOW_SIGNUPS = True
|
|
assert adapter.is_open_for_signup(None, None)
|
|
|
|
settings.SOCIALACCOUNT_ALLOW_SIGNUPS = False
|
|
assert not adapter.is_open_for_signup(None, None)
|
|
|
|
def test_get_connect_redirect_url(self) -> None:
|
|
adapter = get_social_adapter()
|
|
assert adapter.get_connect_redirect_url(None, None) == reverse("base")
|
|
|
|
@pytest.mark.django_db
|
|
def test_save_user_adds_groups(
|
|
self,
|
|
settings: Settings,
|
|
mocker: MockerFixture,
|
|
) -> None:
|
|
settings.SOCIAL_ACCOUNT_DEFAULT_GROUPS = ["group1", "group2"]
|
|
Group.objects.create(name="group1")
|
|
adapter = get_social_adapter()
|
|
user = User.objects.create_user("testuser")
|
|
sociallogin = mocker.MagicMock(user=user)
|
|
|
|
user = adapter.save_user(HttpRequest(), sociallogin, None)
|
|
|
|
assert user.groups.count() == 1
|
|
assert user.groups.filter(name="group1").exists()
|
|
assert not user.groups.filter(name="group2").exists()
|
|
|
|
def test_error_logged_on_authentication_error(
|
|
self,
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
adapter = get_social_adapter()
|
|
with caplog.at_level(logging.INFO, logger="paperless.auth"):
|
|
adapter.on_authentication_error(
|
|
HttpRequest(),
|
|
provider="test-provider",
|
|
error="Error",
|
|
exception="Test authentication error",
|
|
)
|
|
assert any("Test authentication error" in msg for msg in caplog.messages)
|
|
|
|
|
|
@pytest.mark.django_db
|
|
class TestDrfTokenStrategy:
|
|
def test_create_access_token_creates_new_token(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- A user with no existing DRF token
|
|
WHEN:
|
|
- create_access_token is called
|
|
THEN:
|
|
- A new token is created and its key is returned
|
|
"""
|
|
user = User.objects.create_user("testuser")
|
|
request = HttpRequest()
|
|
request.user = user
|
|
|
|
strategy = DrfTokenStrategy()
|
|
token_key = strategy.create_access_token(request)
|
|
|
|
assert token_key is not None
|
|
assert Token.objects.filter(user=user).exists()
|
|
assert token_key == Token.objects.get(user=user).key
|
|
|
|
def test_create_access_token_returns_existing_token(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- A user with an existing DRF token
|
|
WHEN:
|
|
- create_access_token is called again
|
|
THEN:
|
|
- The same token key is returned (no new token created)
|
|
"""
|
|
user = User.objects.create_user("testuser")
|
|
existing_token = Token.objects.create(user=user)
|
|
|
|
request = HttpRequest()
|
|
request.user = user
|
|
|
|
strategy = DrfTokenStrategy()
|
|
token_key = strategy.create_access_token(request)
|
|
|
|
assert token_key == existing_token.key
|
|
assert Token.objects.filter(user=user).count() == 1
|
|
|
|
def test_create_access_token_returns_none_for_unauthenticated_user(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- An unauthenticated request
|
|
WHEN:
|
|
- create_access_token is called
|
|
THEN:
|
|
- None is returned and no token is created
|
|
"""
|
|
request = HttpRequest()
|
|
request.user = AnonymousUser()
|
|
|
|
strategy = DrfTokenStrategy()
|
|
token_key = strategy.create_access_token(request)
|
|
|
|
assert token_key is None
|
|
assert Token.objects.count() == 0
|