Refactor stats tests into separate file with random data

- Add random_ip_address() function to ivatar.utils for generating random IP addresses
- Create separate test_views_stats.py file with StatsTester class
- Move all stats tests from test_views.py to test_views_stats.py
- Update tests to use random_string() for emails and OpenIDs instead of static @example.com
- Update tests to use random_ip_address() for IP addresses instead of static 192.168.1.x
- Remove stats tests from original test_views.py file
This commit is contained in:
Oliver Falk
2025-09-23 16:47:56 +02:00
parent 9d647fe075
commit 4a684f9947
3 changed files with 386 additions and 328 deletions

View File

@@ -7,7 +7,6 @@ import contextlib
# pylint: disable=too-many-lines # pylint: disable=too-many-lines
import os import os
import json
import django import django
from django.urls import reverse from django.urls import reverse
from django.test import TestCase from django.test import TestCase
@@ -68,333 +67,6 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
# msg_prefix="Why does an invalid hash not redirect to deadbeef?", # msg_prefix="Why does an invalid hash not redirect to deadbeef?",
# ) # )
def test_stats_basic(self):
"""
Test basic stats functionality
"""
response = self.client.get("/stats/", follow=True)
self.assertEqual(response.status_code, 200, "unable to fetch stats!")
j = json.loads(response.content)
self.assertEqual(j["users"], 1, "user count incorrect")
self.assertEqual(j["mails"], 0, "mails count incorrect")
self.assertEqual(j["openids"], 0, "openids count incorrect")
self.assertEqual(j["unconfirmed_mails"], 0, "unconfirmed mails count incorrect")
self.assertEqual(
j["unconfirmed_openids"], 0, "unconfirmed openids count incorrect"
)
self.assertEqual(j["avatars"], 0, "avatars count incorrect")
def test_stats_comprehensive(self):
"""
Test comprehensive stats with actual data
"""
from ivatar.ivataraccount.models import (
ConfirmedEmail,
ConfirmedOpenId,
Photo,
UnconfirmedEmail,
UnconfirmedOpenId,
)
# Create test data
email1 = ConfirmedEmail.objects.create(
user=self.user, email="test1@example.com", ip_address="192.168.1.1"
)
email1.access_count = 100
email1.save()
email2 = ConfirmedEmail.objects.create(
user=self.user, email="test2@example.com", ip_address="192.168.1.2"
)
email2.access_count = 50
email2.save()
openid1 = ConfirmedOpenId.objects.create(
user=self.user, openid="http://test1.example.com/", ip_address="192.168.1.3"
)
openid1.access_count = 75
openid1.save()
# Create photos with valid image data (minimal PNG)
# PNG header + minimal data
png_data = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\tpHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\nIDATx\x9cc```\x00\x00\x00\x04\x00\x01\xdd\x8d\xb4\x1c\x00\x00\x00\x00IEND\xaeB`\x82"
photo1 = Photo.objects.create(
user=self.user, data=png_data, format="png", ip_address="192.168.1.4"
)
photo1.access_count = 200
photo1.save()
photo2 = Photo.objects.create(
user=self.user,
data=png_data, # Same data for testing
format="png", # Same format for testing
ip_address="192.168.1.5",
)
photo2.access_count = 150
photo2.save()
# Associate photos with emails/openids
email1.photo = photo1
email1.save()
email2.photo = photo2
email2.save()
openid1.photo = photo1
openid1.save()
# Create unconfirmed entries
UnconfirmedEmail.objects.create(
user=self.user, email="unconfirmed@example.com", ip_address="192.168.1.6"
)
UnconfirmedOpenId.objects.create(
user=self.user,
openid="http://unconfirmed.example.com/",
ip_address="192.168.1.7",
)
# Test the stats endpoint
response = self.client.get("/stats/")
self.assertEqual(response.status_code, 200, "unable to fetch stats!")
j = json.loads(response.content)
# Test basic counts
self.assertEqual(j["users"], 1, "user count incorrect")
self.assertEqual(j["mails"], 2, "mails count incorrect")
self.assertEqual(j["openids"], 1, "openids count incorrect")
self.assertEqual(j["unconfirmed_mails"], 1, "unconfirmed mails count incorrect")
self.assertEqual(
j["unconfirmed_openids"], 1, "unconfirmed openids count incorrect"
)
self.assertEqual(j["avatars"], 2, "avatars count incorrect")
# Test top viewed avatars
self.assertIn("top_viewed_avatars", j, "top_viewed_avatars missing")
self.assertEqual(
len(j["top_viewed_avatars"]), 2, "should have 2 top viewed avatars"
)
# The top viewed avatar should be the one with highest associated email/openid access count
self.assertEqual(
j["top_viewed_avatars"][0]["access_count"],
100,
"top avatar access count incorrect",
)
# Test top queried emails
self.assertIn("top_queried_emails", j, "top_queried_emails missing")
self.assertEqual(
len(j["top_queried_emails"]), 2, "should have 2 top queried emails"
)
self.assertEqual(
j["top_queried_emails"][0]["access_count"],
100,
"top email access count incorrect",
)
# Test top queried openids
self.assertIn("top_queried_openids", j, "top_queried_openids missing")
self.assertEqual(
len(j["top_queried_openids"]), 1, "should have 1 top queried openid"
)
self.assertEqual(
j["top_queried_openids"][0]["access_count"],
75,
"top openid access count incorrect",
)
# Test photo format distribution
self.assertIn(
"photo_format_distribution", j, "photo_format_distribution missing"
)
formats = {
item["format"]: item["count"] for item in j["photo_format_distribution"]
}
self.assertEqual(formats["png"], 2, "png format count incorrect")
# Test user activity stats
self.assertIn("user_activity", j, "user_activity missing")
self.assertEqual(
j["user_activity"]["users_with_multiple_photos"],
1,
"users with multiple photos incorrect",
)
self.assertEqual(
j["user_activity"]["users_with_both_email_and_openid"],
1,
"users with both email and openid incorrect",
)
self.assertEqual(
j["user_activity"]["average_photos_per_user"],
2.0,
"average photos per user incorrect",
)
# Test Bluesky handles (should be empty)
self.assertIn("bluesky_handles", j, "bluesky_handles missing")
self.assertEqual(
j["bluesky_handles"]["total_bluesky_handles"],
0,
"total bluesky handles should be 0",
)
# Test photo size stats
self.assertIn("photo_size_stats", j, "photo_size_stats missing")
self.assertGreater(
j["photo_size_stats"]["average_size_bytes"],
0,
"average photo size should be > 0",
)
self.assertEqual(
j["photo_size_stats"]["total_photos_analyzed"],
2,
"total photos analyzed incorrect",
)
# Test potential duplicate photos
self.assertIn(
"potential_duplicate_photos", j, "potential_duplicate_photos missing"
)
self.assertEqual(
j["potential_duplicate_photos"]["potential_duplicate_groups"],
1,
"should have 1 duplicate group (same PNG data)",
)
def test_stats_edge_cases(self):
"""
Test edge cases for stats
"""
# Test with no data
response = self.client.get("/stats/")
self.assertEqual(response.status_code, 200, "unable to fetch stats!")
j = json.loads(response.content)
# All lists should be empty
self.assertEqual(
len(j["top_viewed_avatars"]), 0, "top_viewed_avatars should be empty"
)
self.assertEqual(
len(j["top_queried_emails"]), 0, "top_queried_emails should be empty"
)
self.assertEqual(
len(j["top_queried_openids"]), 0, "top_queried_openids should be empty"
)
self.assertEqual(
len(j["photo_format_distribution"]),
0,
"photo_format_distribution should be empty",
)
self.assertEqual(
j["bluesky_handles"]["total_bluesky_handles"],
0,
"bluesky_handles should be 0",
)
self.assertEqual(
j["photo_size_stats"]["total_photos_analyzed"],
0,
"photo_size_stats should be 0",
)
self.assertEqual(
j["potential_duplicate_photos"]["potential_duplicate_groups"],
0,
"potential_duplicate_photos should be 0",
)
def test_stats_with_bluesky_handles(self):
"""
Test stats with Bluesky handles
"""
from ivatar.ivataraccount.models import ConfirmedEmail, ConfirmedOpenId
# Create email with Bluesky handle
email = ConfirmedEmail.objects.create(
user=self.user, email="bluesky@example.com", ip_address="192.168.1.1"
)
email.bluesky_handle = "test.bsky.social"
email.access_count = 100
email.save()
# Create OpenID with Bluesky handle
openid = ConfirmedOpenId.objects.create(
user=self.user,
openid="http://bluesky.example.com/",
ip_address="192.168.1.2",
)
openid.bluesky_handle = "another.bsky.social"
openid.access_count = 50
openid.save()
response = self.client.get("/stats/")
self.assertEqual(response.status_code, 200, "unable to fetch stats!")
j = json.loads(response.content)
# Test Bluesky handles stats
self.assertEqual(
j["bluesky_handles"]["total_bluesky_handles"],
2,
"total bluesky handles incorrect",
)
self.assertEqual(
j["bluesky_handles"]["bluesky_emails"], 1, "bluesky emails count incorrect"
)
self.assertEqual(
j["bluesky_handles"]["bluesky_openids"],
1,
"bluesky openids count incorrect",
)
self.assertEqual(
len(j["bluesky_handles"]["top_bluesky_handles"]),
2,
"top bluesky handles count incorrect",
)
def test_stats_photo_duplicates(self):
"""
Test potential duplicate photos detection
"""
from ivatar.ivataraccount.models import Photo
# Create photos with same format and size (potential duplicates)
# PNG header + minimal data
png_data = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\tpHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\nIDATx\x9cc```\x00\x00\x00\x04\x00\x01\xdd\x8d\xb4\x1c\x00\x00\x00\x00IEND\xaeB`\x82"
Photo.objects.create(
user=self.user, data=png_data, format="png", ip_address="192.168.1.1"
)
Photo.objects.create(
user=self.user,
data=png_data, # Same size
format="png", # Same format
ip_address="192.168.1.2",
)
Photo.objects.create(
user=self.user,
data=png_data, # Same size but different format
format="png", # Same format for testing
ip_address="192.168.1.3",
)
response = self.client.get("/stats/")
self.assertEqual(response.status_code, 200, "unable to fetch stats!")
j = json.loads(response.content)
# Should detect potential duplicates
self.assertEqual(
j["potential_duplicate_photos"]["potential_duplicate_groups"],
1,
"should have 1 duplicate group",
)
self.assertEqual(
j["potential_duplicate_photos"]["total_potential_duplicate_photos"],
3,
"should have 3 potential duplicate photos",
)
self.assertEqual(
len(j["potential_duplicate_photos"]["potential_duplicate_groups_detail"]),
1,
"should have 1 duplicate group detail",
)
def test_logout(self): def test_logout(self):
""" """
Test if logout works correctly Test if logout works correctly

379
ivatar/test_views_stats.py Normal file
View File

@@ -0,0 +1,379 @@
# -*- coding: utf-8 -*-
"""
Test our StatsView in ivatar.views
"""
import json
import os
import django
from django.test import TestCase
from django.test import Client
from django.contrib.auth.models import User
from ivatar.utils import random_string, random_ip_address
os.environ["DJANGO_SETTINGS_MODULE"] = "ivatar.settings"
django.setup()
class StatsTester(TestCase):
"""
Test class for StatsView
"""
client = Client()
user = None
username = random_string()
password = random_string()
def login(self):
"""
Login as user
"""
self.client.login(username=self.username, password=self.password)
def setUp(self):
"""
Prepare for tests.
- Create user
"""
self.user = User.objects.create_user(
username=self.username,
password=self.password,
)
def test_stats_basic(self):
"""
Test basic stats functionality
"""
response = self.client.get("/stats/", follow=True)
self.assertEqual(response.status_code, 200, "unable to fetch stats!")
j = json.loads(response.content)
self.assertEqual(j["users"], 1, "user count incorrect")
self.assertEqual(j["mails"], 0, "mails count incorrect")
self.assertEqual(j["openids"], 0, "openids count incorrect")
self.assertEqual(j["unconfirmed_mails"], 0, "unconfirmed mails count incorrect")
self.assertEqual(
j["unconfirmed_openids"], 0, "unconfirmed openids count incorrect"
)
self.assertEqual(j["avatars"], 0, "avatars count incorrect")
def test_stats_comprehensive(self):
"""
Test comprehensive stats with actual data
"""
from ivatar.ivataraccount.models import (
ConfirmedEmail,
ConfirmedOpenId,
Photo,
UnconfirmedEmail,
UnconfirmedOpenId,
)
# Create test data with random values
email1 = ConfirmedEmail.objects.create(
user=self.user,
email=f"{random_string()}@{random_string()}.{random_string(2)}",
ip_address=random_ip_address(),
)
email1.access_count = 100
email1.save()
email2 = ConfirmedEmail.objects.create(
user=self.user,
email=f"{random_string()}@{random_string()}.{random_string(2)}",
ip_address=random_ip_address(),
)
email2.access_count = 50
email2.save()
openid1 = ConfirmedOpenId.objects.create(
user=self.user,
openid=f"http://{random_string()}.{random_string()}.org/",
ip_address=random_ip_address(),
)
openid1.access_count = 75
openid1.save()
# Create photos with valid image data (minimal PNG)
# PNG header + minimal data
png_data = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\tpHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\nIDATx\x9cc```\x00\x00\x00\x04\x00\x01\xdd\x8d\xb4\x1c\x00\x00\x00\x00IEND\xaeB`\x82"
photo1 = Photo.objects.create(
user=self.user, data=png_data, format="png", ip_address=random_ip_address()
)
photo1.access_count = 200
photo1.save()
photo2 = Photo.objects.create(
user=self.user,
data=png_data, # Same data for testing
format="png", # Same format for testing
ip_address=random_ip_address(),
)
photo2.access_count = 150
photo2.save()
# Associate photos with emails/openids
email1.photo = photo1
email1.save()
email2.photo = photo2
email2.save()
openid1.photo = photo1
openid1.save()
# Create unconfirmed entries
UnconfirmedEmail.objects.create(
user=self.user,
email=f"{random_string()}@{random_string()}.{random_string(2)}",
ip_address=random_ip_address(),
)
UnconfirmedOpenId.objects.create(
user=self.user,
openid=f"http://{random_string()}.{random_string()}.org/",
ip_address=random_ip_address(),
)
# Test the stats endpoint
response = self.client.get("/stats/")
self.assertEqual(response.status_code, 200, "unable to fetch stats!")
j = json.loads(response.content)
# Test basic counts
self.assertEqual(j["users"], 1, "user count incorrect")
self.assertEqual(j["mails"], 2, "mails count incorrect")
self.assertEqual(j["openids"], 1, "openids count incorrect")
self.assertEqual(j["unconfirmed_mails"], 1, "unconfirmed mails count incorrect")
self.assertEqual(
j["unconfirmed_openids"], 1, "unconfirmed openids count incorrect"
)
self.assertEqual(j["avatars"], 2, "avatars count incorrect")
# Test top viewed avatars
self.assertIn("top_viewed_avatars", j, "top_viewed_avatars missing")
self.assertEqual(
len(j["top_viewed_avatars"]), 2, "should have 2 top viewed avatars"
)
# The top viewed avatar should be the one with highest associated email/openid access count
self.assertEqual(
j["top_viewed_avatars"][0]["access_count"],
100,
"top avatar access count incorrect",
)
# Test top queried emails
self.assertIn("top_queried_emails", j, "top_queried_emails missing")
self.assertEqual(
len(j["top_queried_emails"]), 2, "should have 2 top queried emails"
)
self.assertEqual(
j["top_queried_emails"][0]["access_count"],
100,
"top email access count incorrect",
)
# Test top queried openids
self.assertIn("top_queried_openids", j, "top_queried_openids missing")
self.assertEqual(
len(j["top_queried_openids"]), 1, "should have 1 top queried openid"
)
self.assertEqual(
j["top_queried_openids"][0]["access_count"],
75,
"top openid access count incorrect",
)
# Test photo format distribution
self.assertIn(
"photo_format_distribution", j, "photo_format_distribution missing"
)
formats = {
item["format"]: item["count"] for item in j["photo_format_distribution"]
}
self.assertEqual(formats["png"], 2, "png format count incorrect")
# Test user activity stats
self.assertIn("user_activity", j, "user_activity missing")
self.assertEqual(
j["user_activity"]["users_with_multiple_photos"],
1,
"users with multiple photos incorrect",
)
self.assertEqual(
j["user_activity"]["users_with_both_email_and_openid"],
1,
"users with both email and openid incorrect",
)
self.assertEqual(
j["user_activity"]["average_photos_per_user"],
2.0,
"average photos per user incorrect",
)
# Test Bluesky handles (should be empty)
self.assertIn("bluesky_handles", j, "bluesky_handles missing")
self.assertEqual(
j["bluesky_handles"]["total_bluesky_handles"],
0,
"total bluesky handles should be 0",
)
# Test photo size stats
self.assertIn("photo_size_stats", j, "photo_size_stats missing")
self.assertGreater(
j["photo_size_stats"]["average_size_bytes"],
0,
"average photo size should be > 0",
)
self.assertEqual(
j["photo_size_stats"]["total_photos_analyzed"],
2,
"total photos analyzed incorrect",
)
# Test potential duplicate photos
self.assertIn(
"potential_duplicate_photos", j, "potential_duplicate_photos missing"
)
self.assertEqual(
j["potential_duplicate_photos"]["potential_duplicate_groups"],
1,
"should have 1 duplicate group (same PNG data)",
)
def test_stats_edge_cases(self):
"""
Test edge cases for stats
"""
# Test with no data
response = self.client.get("/stats/")
self.assertEqual(response.status_code, 200, "unable to fetch stats!")
j = json.loads(response.content)
# All lists should be empty
self.assertEqual(
len(j["top_viewed_avatars"]), 0, "top_viewed_avatars should be empty"
)
self.assertEqual(
len(j["top_queried_emails"]), 0, "top_queried_emails should be empty"
)
self.assertEqual(
len(j["top_queried_openids"]), 0, "top_queried_openids should be empty"
)
self.assertEqual(
len(j["photo_format_distribution"]),
0,
"photo_format_distribution should be empty",
)
self.assertEqual(
j["bluesky_handles"]["total_bluesky_handles"],
0,
"bluesky_handles should be 0",
)
self.assertEqual(
j["photo_size_stats"]["total_photos_analyzed"],
0,
"photo_size_stats should be 0",
)
self.assertEqual(
j["potential_duplicate_photos"]["potential_duplicate_groups"],
0,
"potential_duplicate_photos should be 0",
)
def test_stats_with_bluesky_handles(self):
"""
Test stats with Bluesky handles
"""
from ivatar.ivataraccount.models import ConfirmedEmail, ConfirmedOpenId
# Create email with Bluesky handle
email = ConfirmedEmail.objects.create(
user=self.user,
email=f"{random_string()}@{random_string()}.{random_string(2)}",
ip_address=random_ip_address(),
)
email.bluesky_handle = f"{random_string()}.bsky.social"
email.access_count = 100
email.save()
# Create OpenID with Bluesky handle
openid = ConfirmedOpenId.objects.create(
user=self.user,
openid=f"http://{random_string()}.{random_string()}.org/",
ip_address=random_ip_address(),
)
openid.bluesky_handle = f"{random_string()}.bsky.social"
openid.access_count = 50
openid.save()
response = self.client.get("/stats/")
self.assertEqual(response.status_code, 200, "unable to fetch stats!")
j = json.loads(response.content)
# Test Bluesky handles stats
self.assertEqual(
j["bluesky_handles"]["total_bluesky_handles"],
2,
"total bluesky handles incorrect",
)
self.assertEqual(
j["bluesky_handles"]["bluesky_emails"], 1, "bluesky emails count incorrect"
)
self.assertEqual(
j["bluesky_handles"]["bluesky_openids"],
1,
"bluesky openids count incorrect",
)
self.assertEqual(
len(j["bluesky_handles"]["top_bluesky_handles"]),
2,
"top bluesky handles count incorrect",
)
def test_stats_photo_duplicates(self):
"""
Test potential duplicate photos detection
"""
from ivatar.ivataraccount.models import Photo
# Create photos with same format and size (potential duplicates)
# PNG header + minimal data
png_data = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\tpHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\nIDATx\x9cc```\x00\x00\x00\x04\x00\x01\xdd\x8d\xb4\x1c\x00\x00\x00\x00IEND\xaeB`\x82"
Photo.objects.create(
user=self.user, data=png_data, format="png", ip_address=random_ip_address()
)
Photo.objects.create(
user=self.user,
data=png_data, # Same size
format="png", # Same format
ip_address=random_ip_address(),
)
Photo.objects.create(
user=self.user,
data=png_data, # Same size but different format
format="png", # Same format for testing
ip_address=random_ip_address(),
)
response = self.client.get("/stats/")
self.assertEqual(response.status_code, 200, "unable to fetch stats!")
j = json.loads(response.content)
# Should detect potential duplicates
self.assertEqual(
j["potential_duplicate_photos"]["potential_duplicate_groups"],
1,
"should have 1 duplicate group",
)
self.assertEqual(
j["potential_duplicate_photos"]["total_potential_duplicate_photos"],
3,
"should have 3 potential duplicate photos",
)
self.assertEqual(
len(j["potential_duplicate_photos"]["potential_duplicate_groups_detail"]),
1,
"should have 1 duplicate group detail",
)

View File

@@ -111,6 +111,13 @@ def random_string(length=10):
) )
def random_ip_address():
"""
Return a random IP address (IPv4)
"""
return f"{random.randint(1, 254)}.{random.randint(1, 254)}.{random.randint(1, 254)}.{random.randint(1, 254)}"
def openid_variations(openid): def openid_variations(openid):
""" """
Return the various OpenID variations, ALWAYS in the same order: Return the various OpenID variations, ALWAYS in the same order: