652 lines
28 KiB
Python
652 lines
28 KiB
Python
# 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
|
||
|
||
|
||
# ----------------------------------------------------------------------
|
||
# 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
|
||
):
|
||
"""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
|
||
|
||
|
||
@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)
|
||
|
||
|
||
@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})" |