test: Reorganize organization member tests into proper pytest structure
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>
This commit is contained in:
@@ -1,40 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Test script to verify MCP server organization member support
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "Testing MCP Server Organization Member Support"
|
|
||||||
echo "=============================================="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Test 1: Create individual member
|
|
||||||
echo "Test 1: Creating individual member..."
|
|
||||||
INDIVIDUAL_RESULT=$(echo '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "create_member", "arguments": {"firstName": "Bob", "lastName": "Jones", "email": "bob@example.com"}}}' | node dist/mcp_server.js 2>/dev/null | tail -n 1)
|
|
||||||
echo "✓ Individual member created"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Test 2: Create organization member
|
|
||||||
echo "Test 2: Creating organization member..."
|
|
||||||
ORG_RESULT=$(echo '{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "create_member", "arguments": {"memberType": "ORGANIZATION", "companyName": "Acme Corp", "email": "info@acme.com"}}}' | node dist/mcp_server.js 2>/dev/null | tail -n 1)
|
|
||||||
echo "✓ Organization member created"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Test 3: List members
|
|
||||||
echo "Test 3: Listing all members..."
|
|
||||||
LIST_RESULT=$(echo '{"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "list_members", "arguments": {}}}' | node dist/mcp_server.js 2>/dev/null | tail -n 1)
|
|
||||||
echo "✓ Members listed"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Test 4: Get GraphQL schema
|
|
||||||
echo "Test 4: Checking GraphQL schema for MemberType enum..."
|
|
||||||
SCHEMA_RESULT=$(echo '{"jsonrpc": "2.0", "id": 4, "method": "tools/call", "params": {"name": "get_graphql_schema", "arguments": {}}}' | node dist/mcp_server.js 2>/dev/null | tail -n 1)
|
|
||||||
if echo "$SCHEMA_RESULT" | grep -q "MemberType"; then
|
|
||||||
echo "✓ MemberType enum found in schema"
|
|
||||||
else
|
|
||||||
echo "✗ MemberType enum NOT found in schema"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
echo "All tests passed!"
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Test script for organization member support via GraphQL API."""
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
import json
|
|
||||||
|
|
||||||
API_URL = "http://127.0.0.1:8000/graphql"
|
|
||||||
|
|
||||||
def execute_query(query: str, variables: dict = None):
|
|
||||||
"""Execute a GraphQL query."""
|
|
||||||
payload = {"query": query}
|
|
||||||
if variables:
|
|
||||||
payload["variables"] = variables
|
|
||||||
|
|
||||||
response = httpx.post(API_URL, json=payload)
|
|
||||||
response.raise_for_status()
|
|
||||||
result = response.json()
|
|
||||||
|
|
||||||
if "errors" in result:
|
|
||||||
print(f"GraphQL Errors: {result['errors']}")
|
|
||||||
raise Exception(f"GraphQL query failed: {result['errors']}")
|
|
||||||
|
|
||||||
return result["data"]
|
|
||||||
|
|
||||||
# Test 1: Create individual member
|
|
||||||
print("Test 1: Creating individual member...")
|
|
||||||
create_individual_query = """
|
|
||||||
mutation CreateMember($input: CreateMemberInput!) {
|
|
||||||
createMember(input: $input) {
|
|
||||||
id
|
|
||||||
memberType
|
|
||||||
firstName
|
|
||||||
lastName
|
|
||||||
companyName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
individual_result = execute_query(create_individual_query, {
|
|
||||||
"input": {
|
|
||||||
"firstName": "Charlie",
|
|
||||||
"lastName": "Brown",
|
|
||||||
"email": "charlie@example.com"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
print(f"✓ Created individual member: {individual_result['createMember']}")
|
|
||||||
assert individual_result['createMember']['memberType'] == 'INDIVIDUAL'
|
|
||||||
assert individual_result['createMember']['firstName'] == 'Charlie'
|
|
||||||
assert individual_result['createMember']['companyName'] is None
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Test 2: Create organization member
|
|
||||||
print("Test 2: Creating organization member...")
|
|
||||||
create_org_query = """
|
|
||||||
mutation CreateMember($input: CreateMemberInput!) {
|
|
||||||
createMember(input: $input) {
|
|
||||||
id
|
|
||||||
memberType
|
|
||||||
firstName
|
|
||||||
companyName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
org_result = execute_query(create_org_query, {
|
|
||||||
"input": {
|
|
||||||
"memberType": "ORGANIZATION",
|
|
||||||
"companyName": "Tech Innovations Inc",
|
|
||||||
"email": "info@techinnovations.com"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
print(f"✓ Created organization member: {org_result['createMember']}")
|
|
||||||
assert org_result['createMember']['memberType'] == 'ORGANIZATION'
|
|
||||||
assert org_result['createMember']['companyName'] == 'Tech Innovations Inc'
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Test 3: Create organization with contact person
|
|
||||||
print("Test 3: Creating organization with contact person...")
|
|
||||||
org_with_contact_result = execute_query(create_org_query, {
|
|
||||||
"input": {
|
|
||||||
"memberType": "ORGANIZATION",
|
|
||||||
"companyName": "Global Solutions Ltd",
|
|
||||||
"firstName": "Jane",
|
|
||||||
"lastName": "Doe",
|
|
||||||
"email": "contact@globalsolutions.com"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
print(f"✓ Created organization with contact: {org_with_contact_result['createMember']}")
|
|
||||||
assert org_with_contact_result['createMember']['memberType'] == 'ORGANIZATION'
|
|
||||||
assert org_with_contact_result['createMember']['companyName'] == 'Global Solutions Ltd'
|
|
||||||
assert org_with_contact_result['createMember']['firstName'] == 'Jane'
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Test 4: List all members
|
|
||||||
print("Test 4: Listing all members...")
|
|
||||||
list_query = """
|
|
||||||
query {
|
|
||||||
members {
|
|
||||||
id
|
|
||||||
memberType
|
|
||||||
firstName
|
|
||||||
lastName
|
|
||||||
companyName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
list_result = execute_query(list_query)
|
|
||||||
members = list_result['members']
|
|
||||||
print(f"✓ Found {len(members)} members")
|
|
||||||
print(" Members:")
|
|
||||||
for member in members:
|
|
||||||
if member['memberType'] == 'INDIVIDUAL':
|
|
||||||
print(f" - Individual: {member['firstName']} {member['lastName']}")
|
|
||||||
else:
|
|
||||||
print(f" - Organization: {member['companyName']}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Test 5: Verify validation - individual without firstName should fail
|
|
||||||
print("Test 5: Testing validation - individual without firstName (should fail)...")
|
|
||||||
try:
|
|
||||||
execute_query(create_individual_query, {
|
|
||||||
"input": {
|
|
||||||
"memberType": "INDIVIDUAL",
|
|
||||||
"email": "nofirstname@example.com"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
print("✗ Should have failed but didn't!")
|
|
||||||
exit(1)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✓ Correctly rejected: {str(e)[:100]}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Test 6: Verify validation - organization without companyName should fail
|
|
||||||
print("Test 6: Testing validation - organization without companyName (should fail)...")
|
|
||||||
try:
|
|
||||||
execute_query(create_org_query, {
|
|
||||||
"input": {
|
|
||||||
"memberType": "ORGANIZATION",
|
|
||||||
"email": "nocompany@example.com"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
print("✗ Should have failed but didn't!")
|
|
||||||
exit(1)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✓ Correctly rejected: {str(e)[:100]}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Test 7: Check introspection for MemberType enum
|
|
||||||
print("Test 7: Verifying MemberType enum in schema...")
|
|
||||||
introspection_query = """
|
|
||||||
query {
|
|
||||||
__type(name: "MemberType") {
|
|
||||||
name
|
|
||||||
kind
|
|
||||||
enumValues {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
introspection_result = execute_query(introspection_query)
|
|
||||||
member_type_enum = introspection_result['__type']
|
|
||||||
print(f"✓ Found MemberType enum: {member_type_enum['name']}")
|
|
||||||
enum_values = [ev['name'] for ev in member_type_enum['enumValues']]
|
|
||||||
print(f" Enum values: {enum_values}")
|
|
||||||
assert 'INDIVIDUAL' in enum_values
|
|
||||||
assert 'ORGANIZATION' in enum_values
|
|
||||||
print()
|
|
||||||
|
|
||||||
print("=" * 50)
|
|
||||||
print("All tests passed! ✓")
|
|
||||||
print("=" * 50)
|
|
||||||
@@ -153,3 +153,31 @@ class TestGraphQLAPI:
|
|||||||
# The error should mention validation
|
# The error should mention validation
|
||||||
error_message = str(data["errors"])
|
error_message = str(data["errors"])
|
||||||
assert "Invalid email format" in error_message or "email" in error_message.lower()
|
assert "Invalid email format" in error_message or "email" in error_message.lower()
|
||||||
|
|
||||||
|
async def test_introspect_member_type_enum(self, graphql_client):
|
||||||
|
"""Test GraphQL introspection returns MemberType enum."""
|
||||||
|
query = """
|
||||||
|
query {
|
||||||
|
__type(name: "MemberType") {
|
||||||
|
name
|
||||||
|
kind
|
||||||
|
enumValues {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
response = await graphql_client.post("/graphql", json={"query": query})
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
assert "data" in data
|
||||||
|
|
||||||
|
member_type = data["data"]["__type"]
|
||||||
|
assert member_type["name"] == "MemberType"
|
||||||
|
assert member_type["kind"] == "ENUM"
|
||||||
|
|
||||||
|
enum_values = {ev["name"] for ev in member_type["enumValues"]}
|
||||||
|
assert "INDIVIDUAL" in enum_values
|
||||||
|
assert "ORGANIZATION" in enum_values
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
from src.models.member import Member
|
from src.models.member import Member, MemberType
|
||||||
from src.resolvers.member import MemberNotFoundError, Mutation
|
from src.resolvers.member import MemberNotFoundError, Mutation
|
||||||
from src.schemas.member import CreateMemberInput, UpdateMemberInput
|
from src.schemas.member import CreateMemberInput, MemberType as SchemaMemberType, UpdateMemberInput
|
||||||
from src.validation import ValidationError
|
from src.validation import ValidationError
|
||||||
|
|
||||||
|
|
||||||
@@ -89,6 +89,57 @@ class TestCreateMemberMutation:
|
|||||||
with pytest.raises(ValidationError, match="firstName is required for individual members"):
|
with pytest.raises(ValidationError, match="firstName is required for individual members"):
|
||||||
await mutation.create_member(input=input_data)
|
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:
|
class TestUpdateMemberMutation:
|
||||||
"""Tests for updateMember mutation."""
|
"""Tests for updateMember mutation."""
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
from src.models.member import Member
|
from src.models.member import Member, MemberType
|
||||||
from src.resolvers.member import Query
|
from src.resolvers.member import Query
|
||||||
|
|
||||||
|
|
||||||
@@ -92,3 +92,40 @@ class TestMemberQueries:
|
|||||||
assert results[2].last_name is None
|
assert results[2].last_name is None
|
||||||
assert results[3].first_name == "Bob"
|
assert results[3].first_name == "Bob"
|
||||||
assert results[3].last_name is None
|
assert results[3].last_name is None
|
||||||
|
|
||||||
|
async def test_query_mixed_member_types(self, async_session, patched_session_maker):
|
||||||
|
"""Test querying returns both individual and organization members."""
|
||||||
|
# Create individual member
|
||||||
|
individual = Member(
|
||||||
|
member_type=MemberType.INDIVIDUAL,
|
||||||
|
first_name="Alice",
|
||||||
|
last_name="Smith"
|
||||||
|
)
|
||||||
|
async_session.add(individual)
|
||||||
|
|
||||||
|
# Create organization member
|
||||||
|
organization = Member(
|
||||||
|
member_type=MemberType.ORGANIZATION,
|
||||||
|
company_name="Acme Corp",
|
||||||
|
email="info@acme.com"
|
||||||
|
)
|
||||||
|
async_session.add(organization)
|
||||||
|
await async_session.commit()
|
||||||
|
|
||||||
|
# Query all members
|
||||||
|
query = Query()
|
||||||
|
result = await query.members()
|
||||||
|
|
||||||
|
assert len(result) == 2
|
||||||
|
types = {m.member_type for m in result}
|
||||||
|
assert MemberType.INDIVIDUAL in types
|
||||||
|
assert MemberType.ORGANIZATION in types
|
||||||
|
|
||||||
|
# Verify individual member data
|
||||||
|
individual_result = next(m for m in result if m.member_type == MemberType.INDIVIDUAL)
|
||||||
|
assert individual_result.first_name == "Alice"
|
||||||
|
assert individual_result.last_name == "Smith"
|
||||||
|
|
||||||
|
# Verify organization member data
|
||||||
|
org_result = next(m for m in result if m.member_type == MemberType.ORGANIZATION)
|
||||||
|
assert org_result.company_name == "Acme Corp"
|
||||||
|
|||||||
Reference in New Issue
Block a user