Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,8 +358,8 @@ def claim(self):

def get_thumbnail_url(self):
# convenience method for templates
if self.profile_image and self.image_thumbnail:
with suppress(AttributeError, MissingSource, FileNotFoundError, OSError):
with suppress(AttributeError, MissingSource, FileNotFoundError, OSError):
if self.profile_image and self.image_thumbnail:
return getattr(self.image_thumbnail, "url", None)

def get_avatar_url(self):
Expand Down Expand Up @@ -391,8 +391,8 @@ def to_v3_profile_dict(self, role=None):

def get_hq_image_url(self):
# convenience method for templates
if self.hq_image and self.hq_image_render:
with suppress(AttributeError, MissingSource, FileNotFoundError, OSError):
with suppress(AttributeError, MissingSource, FileNotFoundError, OSError):
if self.hq_image and self.hq_image_render:
return getattr(self.hq_image_render, "url", None)

@property
Expand Down
61 changes: 61 additions & 0 deletions users/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import io
from pathlib import Path

from PIL import Image
import pytest

from django.contrib.auth import get_user_model
Expand All @@ -10,6 +14,15 @@
User = get_user_model()


def _png_upload(filename):
"""In-memory PNG, matching the shape the user fixtures upload."""
file = io.BytesIO()
file.name = filename
Image.new("RGBA", size=(100, 100), color=(155, 0, 0)).save(file, "png")
file.seek(0)
return file


def test_regular_user(user):
assert user.is_active is True
assert user.is_staff is False
Expand Down Expand Up @@ -75,6 +88,54 @@ def test_user_model_image_file_size(user):
user.full_clean()


def test_get_thumbnail_url_returns_url_when_file_exists(user):
"""The happy path: a stored profile image resolves to a thumbnail URL."""
assert user.get_thumbnail_url() == user.image_thumbnail.url
assert user.get_avatar_url() == user.image_thumbnail.url


def test_get_thumbnail_url_returns_none_when_file_missing(user):
"""A `profile_image` naming a file absent from storage must not raise.

`image_thumbnail` is an imagekit `ImageSpecField`; evaluating it for
truthiness generates the thumbnail, which opens the source file and
raises `FileNotFoundError` when the object is gone from storage.
"""
Path(user.profile_image.path).unlink()
user.delete_cached_thumbnail()

assert user.get_thumbnail_url() is None


def test_get_avatar_url_falls_back_when_file_missing(user):
"""`get_avatar_url` degrades to the empty string that triggers initials."""
Path(user.profile_image.path).unlink()
user.delete_cached_thumbnail()

assert user.get_avatar_url() == ""


def test_get_hq_image_url_returns_url_when_file_exists(user):
"""The happy path: a stored high-quality image resolves to a render URL."""
user.hq_image.save("hq-user.png", _png_upload("hq-user.png"))

assert user.get_hq_image_url() == user.hq_image_render.url


def test_get_hq_image_url_returns_none_when_file_missing(user):
"""`hq_image_render` raises from the `if` condition just like the thumbnail.

`users/templatetags/avatar_tags.py` calls `get_hq_image_url` on the same
render paths as `get_thumbnail_url`, so a missing high-quality image has to
degrade the same way or the avatar still 500s.
"""
user.hq_image.save("hq-user.png", _png_upload("hq-user.png"))
Path(user.hq_image.path).unlink()
user.delete_cached_hq_render()

assert user.get_hq_image_url() is None


def test_claim(user):
user.claimed = False
user.save()
Expand Down
Loading