2025-11-21 19:56:03 +01:00
|
|
|
"""End-to-end tests for GraphQL API over HTTP."""
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestGraphQLAPI:
|
|
|
|
|
"""E2E tests for complete GraphQL request/response flows."""
|
|
|
|
|
|
|
|
|
|
async def test_query_members_via_http(self, graphql_client):
|
|
|
|
|
"""Test complete query flow via HTTP POST to /graphql."""
|
|
|
|
|
# First create some test data
|
|
|
|
|
create_query = """
|
|
|
|
|
mutation {
|
|
|
|
|
createMember(input: {firstName: "Alice", lastName: "Smith"}) {
|
|
|
|
|
id
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
await graphql_client.post("/graphql", json={"query": create_query})
|
|
|
|
|
|
|
|
|
|
# Query members via HTTP
|
|
|
|
|
query = """
|
|
|
|
|
query {
|
|
|
|
|
members {
|
|
|
|
|
id
|
|
|
|
|
firstName
|
|
|
|
|
lastName
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
response = await graphql_client.post("/graphql", json={"query": query})
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.json()
|
|
|
|
|
assert "data" in data
|
|
|
|
|
assert "members" in data["data"]
|
|
|
|
|
assert len(data["data"]["members"]) == 1
|
|
|
|
|
assert data["data"]["members"][0]["firstName"] == "Alice"
|
|
|
|
|
assert data["data"]["members"][0]["lastName"] == "Smith"
|
|
|
|
|
|
|
|
|
|
async def test_create_member_mutation_via_http(self, graphql_client):
|
|
|
|
|
"""Test complete mutation flow via HTTP POST to /graphql."""
|
|
|
|
|
mutation = """
|
|
|
|
|
mutation {
|
|
|
|
|
createMember(input: {
|
|
|
|
|
firstName: "Bob"
|
|
|
|
|
lastName: "Johnson"
|
|
|
|
|
email: "bob@example.com"
|
|
|
|
|
}) {
|
|
|
|
|
id
|
|
|
|
|
firstName
|
|
|
|
|
lastName
|
|
|
|
|
email
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
response = await graphql_client.post("/graphql", json={"query": mutation})
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.json()
|
|
|
|
|
assert "data" in data
|
|
|
|
|
assert "createMember" in data["data"]
|
|
|
|
|
member = data["data"]["createMember"]
|
|
|
|
|
assert member["firstName"] == "Bob"
|
|
|
|
|
assert member["lastName"] == "Johnson"
|
|
|
|
|
assert member["email"] == "bob@example.com"
|
|
|
|
|
assert member["id"] is not None
|
|
|
|
|
|
|
|
|
|
async def test_graphql_introspection_schema(self, graphql_client):
|
|
|
|
|
"""Test GraphQL introspection query for __schema."""
|
|
|
|
|
query = """
|
|
|
|
|
query {
|
|
|
|
|
__schema {
|
|
|
|
|
queryType {
|
|
|
|
|
name
|
|
|
|
|
}
|
|
|
|
|
mutationType {
|
|
|
|
|
name
|
|
|
|
|
}
|
|
|
|
|
types {
|
|
|
|
|
name
|
|
|
|
|
kind
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
response = await graphql_client.post("/graphql", json={"query": query})
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.json()
|
|
|
|
|
assert "data" in data
|
|
|
|
|
assert "__schema" in data["data"]
|
|
|
|
|
schema = data["data"]["__schema"]
|
|
|
|
|
assert schema["queryType"]["name"] == "Query"
|
|
|
|
|
assert schema["mutationType"]["name"] == "Mutation"
|
|
|
|
|
|
|
|
|
|
# Check that Member type exists
|
|
|
|
|
type_names = [t["name"] for t in schema["types"]]
|
|
|
|
|
assert "Member" in type_names
|
|
|
|
|
|
|
|
|
|
async def test_graphql_introspection_type(self, graphql_client):
|
|
|
|
|
"""Test GraphQL introspection query for __type."""
|
|
|
|
|
query = """
|
|
|
|
|
query {
|
|
|
|
|
__type(name: "Member") {
|
|
|
|
|
name
|
|
|
|
|
kind
|
|
|
|
|
fields {
|
|
|
|
|
name
|
|
|
|
|
type {
|
|
|
|
|
name
|
|
|
|
|
kind
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
response = await graphql_client.post("/graphql", json={"query": query})
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.json()
|
|
|
|
|
assert "data" in data
|
|
|
|
|
assert "__type" in data["data"]
|
|
|
|
|
member_type = data["data"]["__type"]
|
|
|
|
|
assert member_type["name"] == "Member"
|
|
|
|
|
assert member_type["kind"] == "OBJECT"
|
|
|
|
|
|
|
|
|
|
# Check that expected fields exist
|
|
|
|
|
field_names = [f["name"] for f in member_type["fields"]]
|
|
|
|
|
assert "id" in field_names
|
|
|
|
|
assert "firstName" in field_names
|
|
|
|
|
assert "lastName" in field_names
|
|
|
|
|
assert "email" in field_names
|
|
|
|
|
|
|
|
|
|
async def test_validation_error_returns_400(self, graphql_client):
|
|
|
|
|
"""Test that validation errors return proper error response."""
|
|
|
|
|
mutation = """
|
|
|
|
|
mutation {
|
|
|
|
|
createMember(input: {
|
|
|
|
|
firstName: "Charlie"
|
|
|
|
|
email: "invalid-email"
|
|
|
|
|
}) {
|
|
|
|
|
id
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
response = await graphql_client.post("/graphql", json={"query": mutation})
|
|
|
|
|
|
|
|
|
|
# GraphQL typically returns 200 even with errors, but errors are in response
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.json()
|
|
|
|
|
assert "errors" in data
|
|
|
|
|
# The error should mention validation
|
|
|
|
|
error_message = str(data["errors"])
|
|
|
|
|
assert "Invalid email format" in error_message or "email" in error_message.lower()
|
2025-12-04 16:28:05 +01:00
|
|
|
|
|
|
|
|
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
|