import io import json import os import time import zipfile from urllib.parse import urlparse import pytest from botocore.exceptions import ClientError import uuid as _uuid_mod def test_appsync_create_and_list_api(): """Create a GraphQL API and list it.""" from conftest import make_client appsync = make_client("appsync") resp = appsync.create_graphql_api(name="test-api", authenticationType="API_KEY") api = resp["graphqlApi"] assert api["name"] == "test-api" assert api["apiId"] assert api["authenticationType"] == "API_KEY" apis = appsync.list_graphql_apis()["graphqlApis"] assert any(a["apiId"] == api["apiId"] for a in apis) def test_appsync_get_and_delete_api(): from conftest import make_client appsync = make_client("appsync") resp = appsync.create_graphql_api(name="del-api", authenticationType="API_KEY") api_id = resp["graphqlApi"]["apiId"] got = appsync.get_graphql_api(apiId=api_id) assert got["graphqlApi"]["name"] == "del-api" appsync.delete_graphql_api(apiId=api_id) from botocore.exceptions import ClientError with pytest.raises(ClientError): appsync.get_graphql_api(apiId=api_id) def test_appsync_api_key_crud(): from conftest import make_client appsync = make_client("appsync") api = appsync.create_graphql_api(name="key-api", authenticationType="API_KEY")["graphqlApi"] key = appsync.create_api_key(apiId=api["apiId"])["apiKey"] assert key["id"] keys = appsync.list_api_keys(apiId=api["apiId"])["apiKeys"] assert len(keys) >= 1 appsync.delete_api_key(apiId=api["apiId"], id=key["id"]) def test_appsync_data_source_crud(): from conftest import make_client appsync = make_client("appsync") api = appsync.create_graphql_api(name="ds-api", authenticationType="API_KEY")["graphqlApi"] ds = appsync.create_data_source( apiId=api["apiId"], name="myds", type="AMAZON_DYNAMODB", dynamodbConfig={"tableName": "test-table", "awsRegion": "us-east-1"}, )["dataSource"] assert ds["name"] == "myds" got = appsync.get_data_source(apiId=api["apiId"], name="myds") assert got["dataSource"]["name"] == "myds" appsync.delete_data_source(apiId=api["apiId"], name="myds") def test_appsync_graphql_create_and_query(ddb): """Full AppSync flow: create API + data source + resolver, then execute GraphQL.""" from conftest import make_client appsync = make_client("appsync") # Create DynamoDB table ddb.create_table( TableName="gql-users", KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}], AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}], BillingMode="PAY_PER_REQUEST", ) # Create API api = appsync.create_graphql_api(name="gql-test", authenticationType="API_KEY")["graphqlApi"] api_id = api["apiId"] # Create API key key = appsync.create_api_key(apiId=api_id)["apiKey"] # Create data source appsync.create_data_source( apiId=api_id, name="usersDS", type="AMAZON_DYNAMODB", dynamodbConfig={"tableName": "gql-users", "awsRegion": "us-east-1"}, ) # Create resolvers appsync.create_resolver( apiId=api_id, typeName="Mutation", fieldName="createUser", dataSourceName="usersDS", ) appsync.create_resolver( apiId=api_id, typeName="Query", fieldName="getUser", dataSourceName="usersDS", ) appsync.create_resolver( apiId=api_id, typeName="Query", fieldName="listUsers", dataSourceName="usersDS", ) # Execute mutation via HTTP import urllib.request, json as _json mutation = _json.dumps({ "query": 'mutation CreateUser { createUser(input: {id: "u1", name: "Alice", email: "alice@example.com"}) { id name email } }', }).encode() req = urllib.request.Request( f"http://localhost:4566/v1/apis/{api_id}/graphql", data=mutation, headers={"Content-Type": "application/json", "x-api-key": key["id"]}, ) with urllib.request.urlopen(req) as r: resp = _json.loads(r.read()) assert "data" in resp assert resp["data"]["createUser"]["name"] == "Alice" # Query query = _json.dumps({ "query": 'query GetUser { getUser(id: "u1") { id name email } }', }).encode() req = urllib.request.Request( f"http://localhost:4566/v1/apis/{api_id}/graphql", data=query, headers={"Content-Type": "application/json", "x-api-key": key["id"]}, ) with urllib.request.urlopen(req) as r: resp = _json.loads(r.read()) assert resp["data"]["getUser"]["name"] == "Alice" assert resp["data"]["getUser"]["id"] == "u1" # List list_q = _json.dumps({ "query": "query ListUsers { listUsers { items { id name } } }", }).encode() req = urllib.request.Request( f"http://localhost:4566/v1/apis/{api_id}/graphql", data=list_q, headers={"Content-Type": "application/json", "x-api-key": key["id"]}, ) with urllib.request.urlopen(req) as r: resp = _json.loads(r.read()) items = resp["data"]["listUsers"]["items"] assert len(items) >= 1 assert any(u["name"] == "Alice" for u in items) def test_appsync_graphql_update_mutation(ddb): """Update an existing item via GraphQL mutation.""" import urllib.request, json as _json from conftest import make_client appsync = make_client("appsync") try: ddb.create_table(TableName="gql-update", KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}], AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}], BillingMode="PAY_PER_REQUEST") except Exception: pass api = appsync.create_graphql_api(name="gql-upd", authenticationType="API_KEY")["graphqlApi"] key = appsync.create_api_key(apiId=api["apiId"])["apiKey"] appsync.create_data_source(apiId=api["apiId"], name="ds", type="AMAZON_DYNAMODB", dynamodbConfig={"tableName": "gql-update", "awsRegion": "us-east-1"}) appsync.create_resolver(apiId=api["apiId"], typeName="Mutation", fieldName="createItem", dataSourceName="ds") appsync.create_resolver(apiId=api["apiId"], typeName="Mutation", fieldName="updateItem", dataSourceName="ds") appsync.create_resolver(apiId=api["apiId"], typeName="Query", fieldName="getItem", dataSourceName="ds") def gql(query): req = urllib.request.Request(f"http://localhost:4566/v1/apis/{api['apiId']}/graphql", data=_json.dumps({"query": query}).encode(), headers={"Content-Type": "application/json"}) with urllib.request.urlopen(req) as r: return _json.loads(r.read()) # Create gql('mutation { createItem(input: {id: "i1", title: "Original"}) { id title } }') # Update resp = gql('mutation { updateItem(input: {id: "i1", title: "Updated"}) { id title } }') assert resp["data"]["updateItem"]["title"] == "Updated" # Verify via get resp = gql('query { getItem(id: "i1") { id title } }') assert resp["data"]["getItem"]["title"] == "Updated" def test_appsync_graphql_delete_mutation(ddb): """Delete an item via GraphQL mutation.""" import urllib.request, json as _json from conftest import make_client appsync = make_client("appsync") try: ddb.create_table(TableName="gql-del", KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}], AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}], BillingMode="PAY_PER_REQUEST") except Exception: pass api = appsync.create_graphql_api(name="gql-del", authenticationType="API_KEY")["graphqlApi"] appsync.create_data_source(apiId=api["apiId"], name="ds", type="AMAZON_DYNAMODB", dynamodbConfig={"tableName": "gql-del", "awsRegion": "us-east-1"}) appsync.create_resolver(apiId=api["apiId"], typeName="Mutation", fieldName="createItem", dataSourceName="ds") appsync.create_resolver(apiId=api["apiId"], typeName="Mutation", fieldName="deleteItem", dataSourceName="ds") appsync.create_resolver(apiId=api["apiId"], typeName="Query", fieldName="getItem", dataSourceName="ds") def gql(query): req = urllib.request.Request(f"http://localhost:4566/v1/apis/{api['apiId']}/graphql", data=_json.dumps({"query": query}).encode(), headers={"Content-Type": "application/json"}) with urllib.request.urlopen(req) as r: return _json.loads(r.read()) gql('mutation { createItem(input: {id: "d1", title: "Doomed"}) { id } }') resp = gql('mutation { deleteItem(input: {id: "d1"}) { id title } }') assert resp["data"]["deleteItem"]["id"] == "d1" # Verify deleted resp = gql('query { getItem(id: "d1") { id } }') assert resp["data"]["getItem"] is None def test_appsync_graphql_with_variables(): """GraphQL query using $variables.""" import urllib.request, json as _json from conftest import make_client appsync = make_client("appsync") ddb_client = make_client("dynamodb") try: ddb_client.create_table(TableName="gql-vars", KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}], AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}], BillingMode="PAY_PER_REQUEST") except Exception: pass api = appsync.create_graphql_api(name="gql-vars", authenticationType="API_KEY")["graphqlApi"] appsync.create_data_source(apiId=api["apiId"], name="ds", type="AMAZON_DYNAMODB", dynamodbConfig={"tableName": "gql-vars", "awsRegion": "us-east-1"}) appsync.create_resolver(apiId=api["apiId"], typeName="Mutation", fieldName="createItem", dataSourceName="ds") appsync.create_resolver(apiId=api["apiId"], typeName="Query", fieldName="getItem", dataSourceName="ds") def gql(query, variables=None): body = {"query": query} if variables: body["variables"] = variables req = urllib.request.Request(f"http://localhost:4566/v1/apis/{api['apiId']}/graphql", data=_json.dumps(body).encode(), headers={"Content-Type": "application/json"}) with urllib.request.urlopen(req) as r: return _json.loads(r.read()) gql('mutation { createItem(input: {id: "v1", name: "Var Test"}) { id } }') resp = gql('query GetItem($id: ID!) { getItem(id: $id) { id name } }', {"id": "v1"}) assert resp["data"]["getItem"]["name"] == "Var Test" def test_appsync_graphql_nonexistent_item(): """Query for a non-existent item returns null.""" import urllib.request, json as _json from conftest import make_client appsync = make_client("appsync") ddb_client = make_client("dynamodb") try: ddb_client.create_table(TableName="gql-404", KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}], AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}], BillingMode="PAY_PER_REQUEST") except Exception: pass api = appsync.create_graphql_api(name="gql-404", authenticationType="API_KEY")["graphqlApi"] appsync.create_data_source(apiId=api["apiId"], name="ds", type="AMAZON_DYNAMODB", dynamodbConfig={"tableName": "gql-404", "awsRegion": "us-east-1"}) appsync.create_resolver(apiId=api["apiId"], typeName="Query", fieldName="getItem", dataSourceName="ds") req = urllib.request.Request(f"http://localhost:4566/v1/apis/{api['apiId']}/graphql", data=_json.dumps({"query": 'query { getItem(id: "ghost") { id } }'}).encode(), headers={"Content-Type": "application/json"}) with urllib.request.urlopen(req) as r: resp = _json.loads(r.read()) assert resp["data"]["getItem"] is None def test_appsync_graphql_nonexistent_api(): """Query against a non-existent API returns 404.""" import urllib.request, json as _json req = urllib.request.Request("http://localhost:4566/v1/apis/fake-api-id/graphql", data=_json.dumps({"query": "{ getItem(id: \"1\") { id } }"}).encode(), headers={"Content-Type": "application/json"}) try: urllib.request.urlopen(req) assert False, "Should have failed" except urllib.error.HTTPError as e: assert e.code == 404 def test_appsync_graphql_empty_query(): """Empty query returns 400.""" import urllib.request, json as _json from conftest import make_client appsync = make_client("appsync") api = appsync.create_graphql_api(name="gql-empty", authenticationType="API_KEY")["graphqlApi"] req = urllib.request.Request(f"http://localhost:4566/v1/apis/{api['apiId']}/graphql", data=_json.dumps({"query": ""}).encode(), headers={"Content-Type": "application/json"}) try: urllib.request.urlopen(req) assert False, "Should have failed" except urllib.error.HTTPError as e: assert e.code == 400