From 4165729d61524ca12c83c01d6603497f6a526493 Mon Sep 17 00:00:00 2001 From: Bernard Siebens Date: Sun, 12 Jul 2026 00:20:27 +0200 Subject: [PATCH] Add tests for uncovered members code paths Close the small coverage gaps left after the members app move: - FamilyMembership.__str__ string representation - FamilyAdmin.member_count display (with and without members) - CSV import skips a row with an empty required field (was only exercising the invalid-date path) - MemberImportResult.successful_rows property Coverage 98% -> 99% (69 tests). The only remaining uncovered lines are the redundant re-link branch in MemberCsvImporter.import_row (131-132), which is unreachable because update_or_create already sets member.user. Co-Authored-By: Claude Opus 4.8 --- members/tests.py | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/members/tests.py b/members/tests.py index 658cff2..021e23d 100644 --- a/members/tests.py +++ b/members/tests.py @@ -3,6 +3,7 @@ from datetime import date from io import StringIO from pathlib import Path +from django.contrib.admin.sites import AdminSite from django.core.management import call_command from django.core.management.base import CommandError from django.db import IntegrityError @@ -11,7 +12,9 @@ from django.test import TestCase from authentication.models import User from club.models import Club, ClubMembership +from members.admin import FamilyAdmin from members.models import Family, FamilyMembership, Member +from members.services import MemberImportResult # Create your tests here. @@ -167,6 +170,13 @@ class FamilyMembershipModelTests(TestCase): self.assertEqual(membership.role, FamilyMembership.FamilyRole.PARENT) + def test_str_includes_family_member_and_role(self): + family = Family.objects.create(name="The Smiths") + member = Member.objects.create(first_name="Pat", last_name="Smith") + membership = FamilyMembership.objects.create(family=family, member=member, role=FamilyMembership.FamilyRole.GUARDIAN) + + self.assertEqual(str(membership), "The Smiths - Pat Smith (guardian)") + def test_member_unique_per_family(self): family = Family.objects.create(name="Fam") member = Member.objects.create(first_name="Solo", last_name="Once") @@ -197,6 +207,22 @@ class FamilyMembershipModelTests(TestCase): self.assertTrue(Member.objects.filter(pk=member.pk).exists()) +class FamilyAdminTests(TestCase): + def test_member_count_reflects_memberships(self): + family = Family.objects.create(name="The Smiths") + for i in range(3): + member = Member.objects.create(first_name=f"Kid{i}", last_name="Smith") + FamilyMembership.objects.create(family=family, member=member, role=FamilyMembership.FamilyRole.CHILD) + + admin_instance = FamilyAdmin(Family, AdminSite()) + self.assertEqual(admin_instance.member_count(family), 3) + + def test_member_count_is_zero_without_members(self): + family = Family.objects.create(name="Empty") + admin_instance = FamilyAdmin(Family, AdminSite()) + self.assertEqual(admin_instance.member_count(family), 0) + + class AdminSmokeTests(TestCase): """Exercise the admin config end-to-end to catch misregistration (bad search_fields, autocomplete targets, fieldsets, custom forms).""" @@ -493,3 +519,28 @@ class ImportMembersCsvCommandTests(TestCase): ) self.assertIn("CSV file is empty or missing a header row.", str(context.exception)) + + def test_import_skips_row_with_empty_required_field(self): + csv_path = self.write_csv( + "\n".join( + [ + "first_name,last_name,email,date_of_birth,create_account,club_name,license_number", + "Jane,Doe,jane@example.com,2010-04-12,false,City Swim Club,LIC-001", + ",Missing,nofirst@example.com,2011-05-13,false,City Swim Club,LIC-002", + ] + ) + ) + + stdout, stderr = self.call_import_command(csv_path) + + self.assertIn("Row 3 skipped:", stderr) + self.assertIn("first_name is required.", stderr) + self.assertIn("Members created: 1.", stdout) + self.assertIn("Rows skipped: 1.", stdout) + self.assertFalse(Member.objects.filter(email="nofirst@example.com").exists()) + + +class MemberImportResultTests(TestCase): + def test_successful_rows_sums_created_and_updated(self): + result = MemberImportResult(created_members=2, updated_members=3) + self.assertEqual(result.successful_rows, 5)