feat(backend): create improved tests

This commit is contained in:
2025-08-29 23:50:44 -04:00
parent 1dbfbb9ce6
commit b25191d99a
9 changed files with 3031 additions and 177 deletions

View File

@@ -1,69 +1,652 @@
# tests/test_service_availability.py
# backend/tests/repositories/test_service_availability.py
# ------------------------------------------------------------
# Comprehensive pytest suite for the ServiceAvailabilityRepository.
# ------------------------------------------------------------
import pytest
from typing import List
from backend.models import ServiceAvailability as ServiceAvailabilityModel
from backend.repositories import ServiceAvailabilityRepository
def test_grant_and_revoke(
service_availability_repo,
member_repo,
service_type_repo,
# ----------------------------------------------------------------------
# Helper fixtures for test data
# ----------------------------------------------------------------------
@pytest.fixture
def clean_service_availability(service_availability_repo: ServiceAvailabilityRepository):
"""Clean the ServiceAvailability table for tests that need isolation."""
# Clear any existing service availability records to start fresh
service_availability_repo.db.execute(f"DELETE FROM {service_availability_repo._TABLE}")
service_availability_repo.db._conn.commit()
# ----------------------------------------------------------------------
# 1⃣ Basic CRUD create, get, delete
# ----------------------------------------------------------------------
def test_create_and_get(service_availability_repo: ServiceAvailabilityRepository):
"""Test basic service availability creation and retrieval."""
# Create a new availability record
availability = service_availability_repo.create(member_id=1, service_type_id=1)
# Verify creation
assert isinstance(availability.ServiceAvailabilityId, int)
assert availability.ServiceAvailabilityId > 0
assert availability.MemberId == 1
assert availability.ServiceTypeId == 1
# Retrieve the same record
fetched = service_availability_repo.get(member_id=1, service_type_id=1)
assert fetched is not None
assert fetched.ServiceAvailabilityId == availability.ServiceAvailabilityId
assert fetched.MemberId == 1
assert fetched.ServiceTypeId == 1
def test_get_returns_none_when_missing(service_availability_repo: ServiceAvailabilityRepository):
"""Test that get returns None for nonexistent member/service type pairs."""
result = service_availability_repo.get(member_id=999, service_type_id=999)
assert result is None
def test_create_is_idempotent(service_availability_repo: ServiceAvailabilityRepository):
"""Test that create returns existing record if pair already exists."""
# Create first record
first = service_availability_repo.create(member_id=1, service_type_id=1)
# Create again with same parameters - should return existing record
second = service_availability_repo.create(member_id=1, service_type_id=1)
# Should be the same record
assert first.ServiceAvailabilityId == second.ServiceAvailabilityId
assert first.MemberId == second.MemberId
assert first.ServiceTypeId == second.ServiceTypeId
def test_delete_by_id(service_availability_repo: ServiceAvailabilityRepository):
"""Test deleting availability record by primary key."""
# Create a record
availability = service_availability_repo.create(member_id=1, service_type_id=1)
original_id = availability.ServiceAvailabilityId
# Verify it exists
assert service_availability_repo.get(member_id=1, service_type_id=1) is not None
# Delete it
service_availability_repo.delete(original_id)
# Verify it's gone
assert service_availability_repo.get(member_id=1, service_type_id=1) is None
def test_delete_nonexistent_record(service_availability_repo: ServiceAvailabilityRepository):
"""Test deleting a nonexistent record (should not raise error)."""
# This should not raise an exception
service_availability_repo.delete(99999)
# ----------------------------------------------------------------------
# 2⃣ Grant and revoke operations
# ----------------------------------------------------------------------
def test_grant_and_revoke(service_availability_repo: ServiceAvailabilityRepository):
"""Test the grant and revoke convenience methods."""
# Grant access
granted = service_availability_repo.grant(member_id=1, service_type_id=1)
assert granted.MemberId == 1
assert granted.ServiceTypeId == 1
# Verify it was granted
fetched = service_availability_repo.get(member_id=1, service_type_id=1)
assert fetched is not None
assert fetched.ServiceAvailabilityId == granted.ServiceAvailabilityId
# Revoke access
service_availability_repo.revoke(member_id=1, service_type_id=1)
# Verify it was revoked
assert service_availability_repo.get(member_id=1, service_type_id=1) is None
def test_grant_is_idempotent(service_availability_repo: ServiceAvailabilityRepository):
"""Test that grant is idempotent (multiple calls return same record)."""
# Grant access twice
first_grant = service_availability_repo.grant(member_id=1, service_type_id=1)
second_grant = service_availability_repo.grant(member_id=1, service_type_id=1)
# Should return the same record
assert first_grant.ServiceAvailabilityId == second_grant.ServiceAvailabilityId
assert first_grant.MemberId == second_grant.MemberId
assert first_grant.ServiceTypeId == second_grant.ServiceTypeId
def test_revoke_nonexistent_record(service_availability_repo: ServiceAvailabilityRepository):
"""Test revoking a nonexistent member/service type pair (should not raise error)."""
# This should not raise an exception
service_availability_repo.revoke(member_id=999, service_type_id=999)
# ----------------------------------------------------------------------
# 3⃣ List operations
# ----------------------------------------------------------------------
def test_list_by_member(service_availability_repo: ServiceAvailabilityRepository, clean_service_availability):
"""Test listing all availabilities for a specific member."""
member_id = 1
service_types = [1, 2, 3]
# Grant access to multiple service types
created_records = []
for service_type_id in service_types:
record = service_availability_repo.grant(member_id, service_type_id)
created_records.append(record)
# List all availabilities for the member
member_availabilities = service_availability_repo.list_by_member(member_id)
# Should have all the records we created
assert len(member_availabilities) == 3
member_service_types = {a.ServiceTypeId for a in member_availabilities}
assert member_service_types == set(service_types)
# All should belong to the same member
for availability in member_availabilities:
assert availability.MemberId == member_id
def test_list_by_member_empty(service_availability_repo: ServiceAvailabilityRepository):
"""Test listing availabilities for a member with no records."""
availabilities = service_availability_repo.list_by_member(member_id=999)
assert availabilities == []
def test_list_by_service_type(service_availability_repo: ServiceAvailabilityRepository, clean_service_availability):
"""Test listing all members available for a specific service type."""
service_type_id = 1
member_ids = [1, 2]
# Grant access to multiple members
created_records = []
for member_id in member_ids:
record = service_availability_repo.grant(member_id, service_type_id)
created_records.append(record)
# List all availabilities for the service type
type_availabilities = service_availability_repo.list_by_service_type(service_type_id)
# Should have all the records we created
assert len(type_availabilities) == 2
available_members = {a.MemberId for a in type_availabilities}
assert available_members == set(member_ids)
# All should be for the same service type
for availability in type_availabilities:
assert availability.ServiceTypeId == service_type_id
def test_list_by_service_type_empty(service_availability_repo: ServiceAvailabilityRepository):
"""Test listing availabilities for a service type with no records."""
availabilities = service_availability_repo.list_by_service_type(service_type_id=999)
assert availabilities == []
def test_list_all(service_availability_repo: ServiceAvailabilityRepository, clean_service_availability):
"""Test listing all service availability records."""
# Create multiple records
test_data = [
(1, 1), (1, 2), (1, 3), # Member 1 available for types 1,2,3
(2, 1), (2, 2), # Member 2 available for types 1,2
]
created_records = []
for member_id, service_type_id in test_data:
record = service_availability_repo.grant(member_id, service_type_id)
created_records.append(record)
# List all records
all_records = service_availability_repo.list_all()
# Should have all the records we created
assert len(all_records) == len(test_data)
# Verify all our records are present
created_ids = {r.ServiceAvailabilityId for r in created_records}
fetched_ids = {r.ServiceAvailabilityId for r in all_records}
assert created_ids.issubset(fetched_ids)
def test_list_all_empty_table(service_availability_repo: ServiceAvailabilityRepository, clean_service_availability):
"""Test list_all when table is empty."""
all_records = service_availability_repo.list_all()
assert all_records == []
# ----------------------------------------------------------------------
# 4⃣ members_for_type helper method
# ----------------------------------------------------------------------
def test_members_for_type(service_availability_repo: ServiceAvailabilityRepository, clean_service_availability):
"""Test the members_for_type helper method."""
service_type_id = 1
member_ids = [1, 2] # Valid member IDs only
# Grant access to multiple members
for member_id in member_ids:
service_availability_repo.grant(member_id, service_type_id)
# Get member IDs for the service type
available_members = service_availability_repo.members_for_type(service_type_id)
# Should return the member IDs we granted access to
assert set(available_members) == set(member_ids)
# Should return integers (member IDs)
for member_id in available_members:
assert isinstance(member_id, int)
def test_members_for_type_empty(service_availability_repo: ServiceAvailabilityRepository):
"""Test members_for_type with no available members."""
member_ids = service_availability_repo.members_for_type(service_type_id=999)
assert member_ids == []
def test_members_for_type_ordering(service_availability_repo: ServiceAvailabilityRepository, clean_service_availability):
"""Test that members_for_type returns consistent ordering."""
service_type_id = 1
member_ids = [2, 1] # Create in non-sequential order
# Grant access in the specified order
for member_id in member_ids:
service_availability_repo.grant(member_id, service_type_id)
# Get member IDs multiple times
results = []
for _ in range(3):
available_members = service_availability_repo.members_for_type(service_type_id)
results.append(available_members)
# All results should be identical (consistent ordering)
for i in range(1, len(results)):
assert results[0] == results[i]
# Should contain all our member IDs
assert set(results[0]) == set(member_ids)
# ----------------------------------------------------------------------
# 5⃣ Edge cases and error conditions
# ----------------------------------------------------------------------
def test_create_with_invalid_member_id(service_availability_repo: ServiceAvailabilityRepository):
"""Test creating availability with invalid member ID raises foreign key error."""
with pytest.raises(Exception): # SQLite IntegrityError for FK constraint
service_availability_repo.create(member_id=999, service_type_id=1)
def test_create_with_invalid_service_type_id(service_availability_repo: ServiceAvailabilityRepository):
"""Test creating availability with invalid service type ID raises foreign key error."""
with pytest.raises(Exception): # SQLite IntegrityError for FK constraint
service_availability_repo.create(member_id=1, service_type_id=999)
def test_create_with_negative_ids(service_availability_repo: ServiceAvailabilityRepository):
"""Test creating availability with negative IDs raises foreign key error."""
with pytest.raises(Exception):
service_availability_repo.create(member_id=-1, service_type_id=1)
with pytest.raises(Exception):
service_availability_repo.create(member_id=1, service_type_id=-1)
def test_create_with_zero_ids(service_availability_repo: ServiceAvailabilityRepository):
"""Test creating availability with zero IDs raises foreign key error."""
with pytest.raises(Exception):
service_availability_repo.create(member_id=0, service_type_id=1)
with pytest.raises(Exception):
service_availability_repo.create(member_id=1, service_type_id=0)
def test_get_with_negative_ids(service_availability_repo: ServiceAvailabilityRepository):
"""Test get with negative IDs returns None."""
assert service_availability_repo.get(member_id=-1, service_type_id=1) is None
assert service_availability_repo.get(member_id=1, service_type_id=-1) is None
def test_get_with_zero_ids(service_availability_repo: ServiceAvailabilityRepository):
"""Test get with zero IDs returns None."""
assert service_availability_repo.get(member_id=0, service_type_id=1) is None
assert service_availability_repo.get(member_id=1, service_type_id=0) is None
def test_delete_with_negative_id(service_availability_repo: ServiceAvailabilityRepository):
"""Test delete with negative ID (should not raise error)."""
service_availability_repo.delete(-1)
def test_delete_with_zero_id(service_availability_repo: ServiceAvailabilityRepository):
"""Test delete with zero ID (should not raise error)."""
service_availability_repo.delete(0)
# ----------------------------------------------------------------------
# 6⃣ Data integrity and consistency tests
# ----------------------------------------------------------------------
def test_unique_constraint_enforcement(service_availability_repo: ServiceAvailabilityRepository):
"""Test that the unique constraint on (MemberId, ServiceTypeId) is enforced."""
# Create first record
first = service_availability_repo.create(member_id=1, service_type_id=1)
# Try to create duplicate - should return existing record due to idempotent behavior
second = service_availability_repo.create(member_id=1, service_type_id=1)
# Should be the same record (idempotent behavior)
assert first.ServiceAvailabilityId == second.ServiceAvailabilityId
# Verify only one record exists
all_records = service_availability_repo.list_all()
matching_records = [r for r in all_records if r.MemberId == 1 and r.ServiceTypeId == 1]
assert len(matching_records) == 1
def test_service_availability_model_data_integrity(service_availability_repo: ServiceAvailabilityRepository):
"""Test that ServiceAvailability model preserves data integrity."""
original_member_id = 1
original_service_type_id = 2
availability = service_availability_repo.create(original_member_id, original_service_type_id)
original_id = availability.ServiceAvailabilityId
# Retrieve and verify data is preserved
retrieved = service_availability_repo.get(original_member_id, original_service_type_id)
assert retrieved is not None
assert retrieved.ServiceAvailabilityId == original_id
assert retrieved.MemberId == original_member_id
assert retrieved.ServiceTypeId == original_service_type_id
# Verify through list operations as well
by_member = service_availability_repo.list_by_member(original_member_id)
matching_by_member = [r for r in by_member if r.ServiceTypeId == original_service_type_id]
assert len(matching_by_member) == 1
assert matching_by_member[0].ServiceAvailabilityId == original_id
by_type = service_availability_repo.list_by_service_type(original_service_type_id)
matching_by_type = [r for r in by_type if r.MemberId == original_member_id]
assert len(matching_by_type) == 1
assert matching_by_type[0].ServiceAvailabilityId == original_id
def test_cross_method_consistency(service_availability_repo: ServiceAvailabilityRepository, clean_service_availability):
"""Test consistency across different query methods."""
# Create a complex availability matrix
test_data = [
(1, 1), (1, 2), # Member 1: types 1,2
(2, 1), (2, 3), # Member 2: types 1,3
]
created_records = []
for member_id, service_type_id in test_data:
record = service_availability_repo.grant(member_id, service_type_id)
created_records.append(record)
# Verify consistency across methods
all_records = service_availability_repo.list_all()
# Check each member's records via list_by_member
for member_id in [1, 2]:
member_records = service_availability_repo.list_by_member(member_id)
member_records_from_all = [r for r in all_records if r.MemberId == member_id]
assert len(member_records) == len(member_records_from_all)
member_ids_direct = {r.ServiceAvailabilityId for r in member_records}
member_ids_from_all = {r.ServiceAvailabilityId for r in member_records_from_all}
assert member_ids_direct == member_ids_from_all
# Check each service type's records via list_by_service_type
for service_type_id in [1, 2, 3]:
type_records = service_availability_repo.list_by_service_type(service_type_id)
type_records_from_all = [r for r in all_records if r.ServiceTypeId == service_type_id]
assert len(type_records) == len(type_records_from_all)
type_ids_direct = {r.ServiceAvailabilityId for r in type_records}
type_ids_from_all = {r.ServiceAvailabilityId for r in type_records_from_all}
assert type_ids_direct == type_ids_from_all
# Verify members_for_type consistency
member_ids = service_availability_repo.members_for_type(service_type_id)
member_ids_from_records = [r.MemberId for r in type_records]
assert set(member_ids) == set(member_ids_from_records)
# ----------------------------------------------------------------------
# 7⃣ Parameterized tests for comprehensive coverage
# ----------------------------------------------------------------------
@pytest.mark.parametrize("member_id,service_type_id", [
(1, 1), (1, 2), (1, 3),
(2, 1), (2, 2), (2, 3),
])
def test_create_and_retrieve_valid_combinations(
service_availability_repo: ServiceAvailabilityRepository,
member_id: int,
service_type_id: int
):
"""
Verify that:
• `grant` adds a new (member, service_type) pair idempotently.
• `revoke` removes the pair.
• The helper `members_for_type` returns the expected IDs.
"""
# ------------------------------------------------------------------
# Arrange fetch the IDs we know exist from the fixture.
# ------------------------------------------------------------------
# Alice is member_id 1, Bob is member_id 2 (AUTOINCREMENT order).
alice_id = 1
bob_id = 2
# Service type IDs correspond to the order we inserted them:
# 9AM → 1, 11AM → 2, 6PM → 3
nine_am_id = 1
eleven_am_id = 2
six_pm_id = 3
# ------------------------------------------------------------------
# Act try granting a *new* availability that wasn't seeded.
# We'll give Alice the 11AM slot (she didn't have it before).
# ------------------------------------------------------------------
new_pair = service_availability_repo.grant(alice_id, eleven_am_id)
# ------------------------------------------------------------------
# Assert the row exists and the helper returns the right member list.
# ------------------------------------------------------------------
assert new_pair.MemberId == alice_id
assert new_pair.ServiceTypeId == eleven_am_id
# `members_for_type` should now contain Alice (1) **and** Bob (2) for 11AM.
members_for_11am = service_availability_repo.members_for_type(eleven_am_id)
assert set(members_for_11am) == {alice_id, bob_id}
# ------------------------------------------------------------------
# Revoke the newly added pair and ensure it disappears.
# ------------------------------------------------------------------
service_availability_repo.revoke(alice_id, eleven_am_id)
# After revocation the 11AM list should contain **only** Bob.
members_after_revoke = service_availability_repo.members_for_type(eleven_am_id)
assert members_after_revoke == [bob_id]
# Also verify that `get` returns None for the removed pair.
assert service_availability_repo.get(alice_id, eleven_am_id) is None
"""Test creating and retrieving various valid member/service type combinations."""
# Create
created = service_availability_repo.create(member_id, service_type_id)
assert created.MemberId == member_id
assert created.ServiceTypeId == service_type_id
assert isinstance(created.ServiceAvailabilityId, int)
assert created.ServiceAvailabilityId > 0
# Retrieve
retrieved = service_availability_repo.get(member_id, service_type_id)
assert retrieved is not None
assert retrieved.ServiceAvailabilityId == created.ServiceAvailabilityId
assert retrieved.MemberId == member_id
assert retrieved.ServiceTypeId == service_type_id
def test_list_by_member(service_availability_repo):
"""
Validate that `list_by_member` returns exactly the slots we seeded.
"""
# Alice (member_id 1) should have 9AM (1) and 6PM (3)
alice_slots = service_availability_repo.list_by_member(1)
alice_type_ids = sorted([s.ServiceTypeId for s in alice_slots])
assert alice_type_ids == [1, 3]
@pytest.mark.parametrize("invalid_member_id,invalid_service_type_id", [
(999, 1), (1, 999), (999, 999),
(-1, 1), (1, -1), (-1, -1),
(0, 1), (1, 0), (0, 0),
])
def test_create_with_invalid_combinations_raises_error(
service_availability_repo: ServiceAvailabilityRepository,
invalid_member_id: int,
invalid_service_type_id: int
):
"""Test creating availability with invalid combinations raises foreign key errors."""
with pytest.raises(Exception): # SQLite IntegrityError for FK constraint
service_availability_repo.create(invalid_member_id, invalid_service_type_id)
# Bob (member_id 2) should have 11AM (2) and 6PM (3)
bob_slots = service_availability_repo.list_by_member(2)
bob_type_ids = sorted([s.ServiceTypeId for s in bob_slots])
assert bob_type_ids == [2, 3]
@pytest.mark.parametrize("member_id", [1, 2])
def test_list_by_member_various_members(service_availability_repo: ServiceAvailabilityRepository, member_id: int):
"""Test list_by_member with various member IDs."""
# Grant access to a service type
service_availability_repo.grant(member_id, service_type_id=1)
# List availabilities
availabilities = service_availability_repo.list_by_member(member_id)
# Should have at least one record (the one we just granted)
assert len(availabilities) >= 1
# All records should belong to the specified member
for availability in availabilities:
assert availability.MemberId == member_id
@pytest.mark.parametrize("service_type_id", [1, 2, 3])
def test_list_by_service_type_various_types(service_availability_repo: ServiceAvailabilityRepository, service_type_id: int):
"""Test list_by_service_type with various service type IDs."""
# Grant access to a member
service_availability_repo.grant(member_id=1, service_type_id=service_type_id)
# List availabilities
availabilities = service_availability_repo.list_by_service_type(service_type_id)
# Should have at least one record (the one we just granted)
assert len(availabilities) >= 1
# All records should be for the specified service type
for availability in availabilities:
assert availability.ServiceTypeId == service_type_id
# ----------------------------------------------------------------------
# 8⃣ Integration and workflow tests
# ----------------------------------------------------------------------
def test_complete_availability_workflow(service_availability_repo: ServiceAvailabilityRepository, clean_service_availability):
"""Test a complete workflow with multiple operations."""
member_id = 1
service_type_ids = [1, 2, 3]
initial_count = len(service_availability_repo.list_all())
# Step 1: Grant access to multiple service types
granted_records = []
for service_type_id in service_type_ids:
record = service_availability_repo.grant(member_id, service_type_id)
granted_records.append(record)
assert record.MemberId == member_id
assert record.ServiceTypeId == service_type_id
# Step 2: Verify records exist in list_all
all_records = service_availability_repo.list_all()
assert len(all_records) == initial_count + 3
granted_ids = {r.ServiceAvailabilityId for r in granted_records}
all_ids = {r.ServiceAvailabilityId for r in all_records}
assert granted_ids.issubset(all_ids)
# Step 3: Verify via list_by_member
member_records = service_availability_repo.list_by_member(member_id)
member_service_types = {r.ServiceTypeId for r in member_records}
assert set(service_type_ids) == member_service_types
# Step 4: Verify via list_by_service_type and members_for_type
for service_type_id in service_type_ids:
type_records = service_availability_repo.list_by_service_type(service_type_id)
type_member_ids = {r.MemberId for r in type_records}
assert member_id in type_member_ids
member_ids_for_type = service_availability_repo.members_for_type(service_type_id)
assert member_id in member_ids_for_type
# Step 5: Revoke access to one service type
revoked_type = service_type_ids[1] # Revoke access to type 2
service_availability_repo.revoke(member_id, revoked_type)
# Step 6: Verify revocation
assert service_availability_repo.get(member_id, revoked_type) is None
updated_member_records = service_availability_repo.list_by_member(member_id)
updated_service_types = {r.ServiceTypeId for r in updated_member_records}
expected_remaining = set(service_type_ids) - {revoked_type}
assert updated_service_types == expected_remaining
# Step 7: Clean up remaining records
for service_type_id in [1, 3]: # Types 1 and 3 should still exist
service_availability_repo.revoke(member_id, service_type_id)
# Step 8: Verify cleanup
final_member_records = service_availability_repo.list_by_member(member_id)
original_member_records = [r for r in final_member_records if r.ServiceAvailabilityId in granted_ids]
assert len(original_member_records) == 0
def test_complex_multi_member_scenario(service_availability_repo: ServiceAvailabilityRepository, clean_service_availability):
"""Test complex scenarios with multiple members and service types."""
# Create a realistic availability matrix:
# Member 1: Available for all service types (1,2,3)
# Member 2: Available for morning services (1,2)
# Member 3: Available for evening service only (3)
# Member 4: Not available for any services
availability_matrix = [
(1, 1), (1, 2), (1, 3), # Member 1: all services
(2, 1), (2, 2), # Member 2: morning only
# Member 3: no services (doesn't exist in seeded data)
]
# Grant all availabilities
for member_id, service_type_id in availability_matrix:
service_availability_repo.grant(member_id, service_type_id)
# Test service type 1 (should have members 1,2)
type1_members = service_availability_repo.members_for_type(1)
assert set(type1_members) == {1, 2}
# Test service type 2 (should have members 1,2)
type2_members = service_availability_repo.members_for_type(2)
assert set(type2_members) == {1, 2}
# Test service type 3 (should have member 1 only)
type3_members = service_availability_repo.members_for_type(3)
assert set(type3_members) == {1}
# Test member 1 (should have all service types)
member1_records = service_availability_repo.list_by_member(1)
member1_types = {r.ServiceTypeId for r in member1_records}
assert member1_types == {1, 2, 3}
# Test member 2 (should have types 1,2)
member2_records = service_availability_repo.list_by_member(2)
member2_types = {r.ServiceTypeId for r in member2_records}
assert member2_types == {1, 2}
# Test nonexistent member (should have no services)
member3_records = service_availability_repo.list_by_member(3)
assert len(member3_records) == 0
# Simulate removing member 1 from evening service
service_availability_repo.revoke(1, 3)
# Type 3 should now have no members
updated_type3_members = service_availability_repo.members_for_type(3)
assert set(updated_type3_members) == set()
# Member 1 should now only have types 1,2
updated_member1_records = service_availability_repo.list_by_member(1)
updated_member1_types = {r.ServiceTypeId for r in updated_member1_records}
assert updated_member1_types == {1, 2}
def test_service_availability_repository_consistency_under_operations(
service_availability_repo: ServiceAvailabilityRepository,
clean_service_availability
):
"""Test repository consistency under various operations."""
# Create, modify, and delete records while verifying consistency
operations = [
('grant', 1, 1),
('grant', 1, 2),
('grant', 2, 1),
('revoke', 1, 1),
('grant', 1, 3),
('revoke', 2, 1),
('grant', 2, 2),
]
expected_state = set() # Track expected (member_id, service_type_id) pairs
for operation, member_id, service_type_id in operations:
if operation == 'grant':
service_availability_repo.grant(member_id, service_type_id)
expected_state.add((member_id, service_type_id))
elif operation == 'revoke':
service_availability_repo.revoke(member_id, service_type_id)
expected_state.discard((member_id, service_type_id))
# Verify consistency after each operation
all_records = service_availability_repo.list_all()
actual_pairs = {(r.MemberId, r.ServiceTypeId) for r in all_records if (r.MemberId, r.ServiceTypeId) in expected_state or (r.MemberId, r.ServiceTypeId) not in expected_state}
# Filter to only the pairs we've been working with
relevant_actual_pairs = {(r.MemberId, r.ServiceTypeId) for r in all_records
if r.MemberId in [1, 2] and r.ServiceTypeId in [1, 2, 3]}
assert relevant_actual_pairs == expected_state, f"Inconsistency after {operation}({member_id}, {service_type_id})"
# Verify each record can be retrieved individually
for member_id_check, service_type_id_check in expected_state:
record = service_availability_repo.get(member_id_check, service_type_id_check)
assert record is not None, f"Could not retrieve ({member_id_check}, {service_type_id_check})"