Fix test organization by moving ad-hoc test scripts into proper pytest tests following project conventions. Added 4 new tests to integration and e2e layers to provide comprehensive coverage for organization members. Changes: - tests/integration/test_member_mutations.py: * Add test_create_organization_member - Tests creating organizations with companyName * Add test_create_organization_with_contact_person - Tests organizations with contact info * Import MemberType from models and schemas - tests/integration/test_member_queries.py: * Add test_query_mixed_member_types - Tests querying both individual and organization members * Import MemberType from models - tests/e2e/test_graphql_api.py: * Add test_introspect_member_type_enum - Tests GraphQL introspection for MemberType enum * Verifies INDIVIDUAL and ORGANIZATION enum values Deleted improper test files: - test_organization_members.py (root) - Ad-hoc script using httpx directly - test_mcp_changes.sh (root) - Shell script for MCP server testing Test results: - All 57 tests pass (up from 53) - Organization members now tested at all layers: unit, integration, and e2e - Tests follow pytest conventions with async patterns and shared fixtures - Integrated with CI/CD pipeline (proper test/ directory structure) The proper test structure ensures: 1. Unit tests validate business logic (validation layer) 2. Integration tests verify GraphQL resolvers and database persistence 3. E2E tests confirm full HTTP request/response flows 4. All tests use shared fixtures from conftest.py 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
254 lines
9.6 KiB
Python
254 lines
9.6 KiB
Python
"""Integration tests for GraphQL mutation resolvers."""
|
|
|
|
import pytest
|
|
from sqlalchemy import select
|
|
|
|
from src.models.member import Member, MemberType
|
|
from src.resolvers.member import MemberNotFoundError, Mutation
|
|
from src.schemas.member import CreateMemberInput, MemberType as SchemaMemberType, UpdateMemberInput
|
|
from src.validation import ValidationError
|
|
|
|
|
|
class TestCreateMemberMutation:
|
|
"""Tests for createMember mutation."""
|
|
|
|
async def test_create_member_with_minimal_data(self, async_session, patched_session_maker):
|
|
"""Test creating a member with only firstName."""
|
|
mutation = Mutation()
|
|
input_data = CreateMemberInput(first_name="Bob")
|
|
|
|
result = await mutation.create_member(input=input_data)
|
|
|
|
assert result.id is not None
|
|
assert result.first_name == "Bob"
|
|
assert result.last_name is None
|
|
assert result.email is None
|
|
assert result.phone is None
|
|
|
|
# Verify in database
|
|
db_result = await async_session.execute(
|
|
select(Member).where(Member.id == result.id)
|
|
)
|
|
db_member = db_result.scalar_one()
|
|
assert db_member.first_name == "Bob"
|
|
|
|
async def test_create_member_with_complete_data(self, patched_session_maker):
|
|
"""Test creating a member with all fields populated."""
|
|
mutation = Mutation()
|
|
input_data = CreateMemberInput(
|
|
first_name="Alice",
|
|
last_name="Johnson",
|
|
street="456 Oak Ave",
|
|
apartment_number="2A",
|
|
zip="54321",
|
|
city="Portland",
|
|
country="USA",
|
|
email="alice@example.com",
|
|
phone="+14155559999",
|
|
)
|
|
|
|
result = await mutation.create_member(input=input_data)
|
|
|
|
assert result.first_name == "Alice"
|
|
assert result.last_name == "Johnson"
|
|
assert result.street == "456 Oak Ave"
|
|
assert result.apartment_number == "2A"
|
|
assert result.zip == "54321"
|
|
assert result.city == "Portland"
|
|
assert result.country == "USA"
|
|
assert result.email == "alice@example.com"
|
|
assert result.phone == "+14155559999"
|
|
|
|
async def test_create_member_with_invalid_email(self, patched_session_maker):
|
|
"""Test creating a member with invalid email raises ValidationError."""
|
|
mutation = Mutation()
|
|
input_data = CreateMemberInput(
|
|
first_name="Charlie",
|
|
email="not-an-email"
|
|
)
|
|
|
|
with pytest.raises(ValidationError, match="Invalid email format"):
|
|
await mutation.create_member(input=input_data)
|
|
|
|
async def test_create_member_with_invalid_phone(self, patched_session_maker):
|
|
"""Test creating a member with invalid phone raises ValidationError."""
|
|
mutation = Mutation()
|
|
input_data = CreateMemberInput(
|
|
first_name="David",
|
|
phone="abc123" # Invalid characters
|
|
)
|
|
|
|
with pytest.raises(ValidationError, match="Invalid phone format"):
|
|
await mutation.create_member(input=input_data)
|
|
|
|
async def test_create_member_with_empty_first_name(self, patched_session_maker):
|
|
"""Test creating a member with empty firstName raises ValidationError."""
|
|
mutation = Mutation()
|
|
input_data = CreateMemberInput(first_name="")
|
|
|
|
with pytest.raises(ValidationError, match="firstName is required for individual members"):
|
|
await mutation.create_member(input=input_data)
|
|
|
|
async def test_create_organization_member(self, async_session, patched_session_maker):
|
|
"""Test creating an organization member with companyName."""
|
|
mutation = Mutation()
|
|
input_data = CreateMemberInput(
|
|
member_type=SchemaMemberType.ORGANIZATION,
|
|
company_name="Tech Innovations Inc",
|
|
email="info@techinnovations.com"
|
|
)
|
|
|
|
result = await mutation.create_member(input=input_data)
|
|
|
|
assert result.id is not None
|
|
assert result.member_type == SchemaMemberType.ORGANIZATION
|
|
assert result.company_name == "Tech Innovations Inc"
|
|
assert result.first_name is None
|
|
|
|
# Verify in database
|
|
db_result = await async_session.execute(
|
|
select(Member).where(Member.id == result.id)
|
|
)
|
|
db_member = db_result.scalar_one()
|
|
assert db_member.member_type.value == "ORGANIZATION"
|
|
assert db_member.company_name == "Tech Innovations Inc"
|
|
|
|
async def test_create_organization_with_contact_person(self, async_session, patched_session_maker):
|
|
"""Test creating an organization with contact person."""
|
|
mutation = Mutation()
|
|
input_data = CreateMemberInput(
|
|
member_type=SchemaMemberType.ORGANIZATION,
|
|
company_name="Global Solutions Ltd",
|
|
first_name="Jane",
|
|
last_name="Doe",
|
|
email="contact@globalsolutions.com"
|
|
)
|
|
|
|
result = await mutation.create_member(input=input_data)
|
|
|
|
assert result.member_type == SchemaMemberType.ORGANIZATION
|
|
assert result.company_name == "Global Solutions Ltd"
|
|
assert result.first_name == "Jane"
|
|
assert result.last_name == "Doe"
|
|
|
|
# Verify in database
|
|
db_result = await async_session.execute(
|
|
select(Member).where(Member.id == result.id)
|
|
)
|
|
db_member = db_result.scalar_one()
|
|
assert db_member.member_type.value == "ORGANIZATION"
|
|
assert db_member.company_name == "Global Solutions Ltd"
|
|
assert db_member.first_name == "Jane"
|
|
|
|
|
|
class TestUpdateMemberMutation:
|
|
"""Tests for updateMember mutation."""
|
|
|
|
async def test_update_member_successfully(self, async_session, patched_session_maker):
|
|
"""Test updating a member's fields successfully."""
|
|
# Create a member
|
|
member = Member(first_name="Eve", email="old@example.com")
|
|
async_session.add(member)
|
|
await async_session.commit()
|
|
await async_session.refresh(member)
|
|
original_created_at = member.created_at
|
|
|
|
# Update the member
|
|
mutation = Mutation()
|
|
input_data = UpdateMemberInput(
|
|
id=member.id,
|
|
email="new@example.com",
|
|
phone="+14155551111"
|
|
)
|
|
|
|
result = await mutation.update_member(input=input_data)
|
|
|
|
assert result.id == member.id
|
|
assert result.first_name == "Eve" # Unchanged
|
|
assert result.email == "new@example.com" # Updated
|
|
assert result.phone == "+14155551111" # Updated
|
|
|
|
# Verify updated_at is set (can't reliably test it changed due to timing)
|
|
assert result.updated_at is not None
|
|
assert result.created_at == original_created_at
|
|
|
|
async def test_update_member_with_nonexistent_id(self, patched_session_maker):
|
|
"""Test updating a non-existent member raises MemberNotFoundError."""
|
|
mutation = Mutation()
|
|
input_data = UpdateMemberInput(id=999, email="test@example.com")
|
|
|
|
with pytest.raises(MemberNotFoundError, match="Member with ID 999 not found"):
|
|
await mutation.update_member(input=input_data)
|
|
|
|
async def test_update_member_partial_data(self, async_session, patched_session_maker):
|
|
"""Test updating only some fields leaves others unchanged."""
|
|
# Create a member with complete data
|
|
member = Member(
|
|
first_name="Frank",
|
|
last_name="Miller",
|
|
email="frank@example.com",
|
|
phone="+14155552222"
|
|
)
|
|
async_session.add(member)
|
|
await async_session.commit()
|
|
await async_session.refresh(member)
|
|
|
|
# Update only email
|
|
mutation = Mutation()
|
|
input_data = UpdateMemberInput(id=member.id, email="updated@example.com")
|
|
|
|
result = await mutation.update_member(input=input_data)
|
|
|
|
assert result.first_name == "Frank" # Unchanged
|
|
assert result.last_name == "Miller" # Unchanged
|
|
assert result.email == "updated@example.com" # Updated
|
|
assert result.phone == "+14155552222" # Unchanged
|
|
|
|
async def test_update_member_with_invalid_email(self, async_session, patched_session_maker):
|
|
"""Test updating with invalid email raises ValidationError."""
|
|
# Create a member
|
|
member = Member(first_name="Grace")
|
|
async_session.add(member)
|
|
await async_session.commit()
|
|
await async_session.refresh(member)
|
|
|
|
# Try to update with invalid email
|
|
mutation = Mutation()
|
|
input_data = UpdateMemberInput(id=member.id, email="invalid-email")
|
|
|
|
with pytest.raises(ValidationError, match="Invalid email format"):
|
|
await mutation.update_member(input=input_data)
|
|
|
|
|
|
class TestDeleteMemberMutation:
|
|
"""Tests for deleteMember mutation."""
|
|
|
|
async def test_delete_member_successfully(self, async_session, patched_session_maker):
|
|
"""Test deleting a member successfully."""
|
|
# Create a member
|
|
member = Member(first_name="Henry")
|
|
async_session.add(member)
|
|
await async_session.commit()
|
|
await async_session.refresh(member)
|
|
member_id = member.id
|
|
|
|
# Delete the member
|
|
mutation = Mutation()
|
|
result = await mutation.delete_member(id=member_id)
|
|
|
|
assert result is True
|
|
|
|
# Verify member is gone
|
|
db_result = await async_session.execute(
|
|
select(Member).where(Member.id == member_id)
|
|
)
|
|
db_member = db_result.scalar_one_or_none()
|
|
assert db_member is None
|
|
|
|
async def test_delete_member_with_nonexistent_id(self, patched_session_maker):
|
|
"""Test deleting a non-existent member raises MemberNotFoundError."""
|
|
mutation = Mutation()
|
|
|
|
with pytest.raises(MemberNotFoundError, match="Member with ID 999 not found"):
|
|
await mutation.delete_member(id=999)
|