#!/usr/bin/env python3 """ Unit tests for the pairing algorithm using pytest """ import pytest import random from app import create_pairs class TestPairingAlgorithm: """Test class for the pairing algorithm""" def test_minimum_names_requirement(self): """Test that at least 3 names are required""" with pytest.raises(ValueError, match="Need at least 3 names"): create_pairs([]) with pytest.raises(ValueError, match="Need at least 3 names"): create_pairs(["Alice"]) with pytest.raises(ValueError, match="Need at least 3 names"): create_pairs(["Alice", "Bob"]) def test_basic_pairing_4_people(self): """Test pairing with 4 people""" names = ["Alice", "Bob", "Charlie", "Diana"] pairs = create_pairs(names) # Should have exactly 4 pairs (4 people * 2 pairs each / 2 = 4 total pairs) assert len(pairs) == 4 # Each person should have exactly 2 pairs person_pair_counts = self._count_pairs_per_person(pairs) for person in names: assert person_pair_counts[person] == 2, f"{person} has {person_pair_counts[person]} pairs, expected 2" # No duplicate pairs self._assert_no_duplicate_pairs(pairs) def test_pairing_5_people(self): """Test pairing with 5 people""" names = ["Alice", "Bob", "Charlie", "Diana", "Eve"] pairs = create_pairs(names) # Should have exactly 5 pairs assert len(pairs) == 5 # Each person should have exactly 2 pairs person_pair_counts = self._count_pairs_per_person(pairs) for person in names: assert person_pair_counts[person] == 2, f"{person} has {person_pair_counts[person]} pairs, expected 2" # No duplicate pairs self._assert_no_duplicate_pairs(pairs) def test_pairing_6_people(self): """Test pairing with 6 people""" names = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank"] pairs = create_pairs(names) # Should have exactly 6 pairs assert len(pairs) == 6 # Each person should have exactly 2 pairs person_pair_counts = self._count_pairs_per_person(pairs) for person in names: assert person_pair_counts[person] == 2, f"{person} has {person_pair_counts[person]} pairs, expected 2" # No duplicate pairs self._assert_no_duplicate_pairs(pairs) def test_pairing_7_people(self): """Test pairing with 7 people""" names = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank", "Grace"] pairs = create_pairs(names) # Should have exactly 7 pairs assert len(pairs) == 7 # Each person should have exactly 2 pairs person_pair_counts = self._count_pairs_per_person(pairs) for person in names: assert person_pair_counts[person] == 2, f"{person} has {person_pair_counts[person]} pairs, expected 2" # No duplicate pairs self._assert_no_duplicate_pairs(pairs) def test_no_self_pairing(self): """Test that no person is paired with themselves""" names = ["Alice", "Bob", "Charlie", "Diana"] pairs = create_pairs(names) for pair in pairs: assert pair["person1"] != pair["person2"], f"Self-pairing found: {pair}" def test_pair_structure(self): """Test that pairs have the correct structure""" names = ["Alice", "Bob", "Charlie"] pairs = create_pairs(names) for pair in pairs: assert "person1" in pair, "Pair missing person1 key" assert "person2" in pair, "Pair missing person2 key" assert isinstance(pair["person1"], str), "person1 should be a string" assert isinstance(pair["person2"], str), "person2 should be a string" def test_randomness(self): """Test that the algorithm produces different results on multiple runs""" names = ["Alice", "Bob", "Charlie", "Diana", "Eve"] # Run multiple times and collect results results = [] for _ in range(10): pairs = create_pairs(names) # Convert to set of tuples for comparison pair_set = {(p["person1"], p["person2"]) for p in pairs} results.append(pair_set) # Check that we get at least 2 different results (indicating randomness) unique_results = set() for result in results: unique_results.add(tuple(sorted(result))) assert len(unique_results) >= 2, "Algorithm doesn't seem to be random" def test_edge_case_odd_number(self): """Test with odd number of people""" names = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank", "Grace"] pairs = create_pairs(names) # Should have exactly 7 pairs assert len(pairs) == 7 # Each person should have exactly 2 pairs person_pair_counts = self._count_pairs_per_person(pairs) for person in names: assert person_pair_counts[person] == 2, f"{person} has {person_pair_counts[person]} pairs, expected 2" def test_edge_case_even_number(self): """Test with even number of people""" names = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank"] pairs = create_pairs(names) # Should have exactly 6 pairs assert len(pairs) == 6 # Each person should have exactly 2 pairs person_pair_counts = self._count_pairs_per_person(pairs) for person in names: assert person_pair_counts[person] == 2, f"{person} has {person_pair_counts[person]} pairs, expected 2" def test_large_group(self): """Test with a larger group""" names = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank", "Grace", "Henry", "Ivy", "Jack"] pairs = create_pairs(names) # Should have exactly 10 pairs assert len(pairs) == 10 # Each person should have exactly 2 pairs person_pair_counts = self._count_pairs_per_person(pairs) for person in names: assert person_pair_counts[person] == 2, f"{person} has {person_pair_counts[person]} pairs, expected 2" # No duplicate pairs self._assert_no_duplicate_pairs(pairs) def test_special_characters_in_names(self): """Test with names containing special characters""" names = ["José", "Mary-Jane", "O'Connor", "李小明"] pairs = create_pairs(names) # Should have exactly 4 pairs assert len(pairs) == 4 # Each person should have exactly 2 pairs person_pair_counts = self._count_pairs_per_person(pairs) for person in names: assert person_pair_counts[person] == 2, f"{person} has {person_pair_counts[person]} pairs, expected 2" def _count_pairs_per_person(self, pairs): """Helper method to count how many pairs each person has""" person_pair_counts = {} for pair in pairs: person1, person2 = pair["person1"], pair["person2"] person_pair_counts[person1] = person_pair_counts.get(person1, 0) + 1 person_pair_counts[person2] = person_pair_counts.get(person2, 0) + 1 return person_pair_counts def _assert_no_duplicate_pairs(self, pairs): """Helper method to assert no duplicate pairs exist""" pair_strings = set() for pair in pairs: pair_str = f"{pair['person1']}-{pair['person2']}" reverse_str = f"{pair['person2']}-{pair['person1']}" assert pair_str not in pair_strings, f"Duplicate pair found: {pair}" assert reverse_str not in pair_strings, f"Duplicate pair found (reverse): {pair}" pair_strings.add(pair_str) class TestPairingProperties: """Test class for mathematical properties of pairing""" def test_total_pairs_formula(self): """Test that total pairs = number of people""" for n in range(3, 11): # Test with 3 to 10 people names = [f"Person{i}" for i in range(n)] pairs = create_pairs(names) # Total pairs should equal number of people assert len(pairs) == n, f"Expected {n} pairs for {n} people, got {len(pairs)}" def test_each_person_has_exactly_two_pairs(self): """Test that each person has exactly 2 pairs""" for n in range(3, 11): # Test with 3 to 10 people names = [f"Person{i}" for i in range(n)] pairs = create_pairs(names) person_pair_counts = {} for pair in pairs: person1, person2 = pair["person1"], pair["person2"] person_pair_counts[person1] = person_pair_counts.get(person1, 0) + 1 person_pair_counts[person2] = person_pair_counts.get(person2, 0) + 1 for person in names: assert person_pair_counts[person] == 2, f"{person} has {person_pair_counts[person]} pairs, expected 2" if __name__ == "__main__": # Run tests with pytest pytest.main([__file__, "-v"])