mirror of
https://git.linux-kernel.at/oliver/ivatar.git
synced 2025-11-17 21:48:02 +00:00
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:
@@ -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
379
ivatar/test_views_stats.py
Normal 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",
|
||||||
|
)
|
||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user