# GraphQL integration testing

The GraphQL plugin allows you to test GraphQL APIs using Tavern. It provides specialized request and response handling
that understands GraphQL's unique structure and requirements.

## Important Note on Query Formatting

**GraphQL queries cannot use Tavern's standard variable formatting (curly braces `{}`)** because GraphQL syntax itself
uses curly braces. If you try to use formatting like `{variable}` in your GraphQL queries, it will fail because Tavern
will attempt to format them before sending the query.

**Instead, use GraphQL variables for any dynamic content:**

```yaml
# ✅ GOOD - Use GraphQL variables
stages:
  - name: Query user with variable
    graphql_request:
      url: "{graphql_server_url}/graphql"    # URL formatting works fine
      query: |
        query GetUser($id: ID!) {
          user(id: $id) {                     # GraphQL variables work fine
            id
            name
            email
          }
        }
      variables:
        id: "{user_id}"                      # Variables in the variables object support formatting

# ❌ BAD - Don't use formatting in queries
stages:
  - name: Query user with formatting in query
    graphql_request:
      url: "{graphql_server_url}/graphql"
      query: |
        query GetUser {
          user(id: "{user_id}") {             # This will fail - formatting in query
            id
            name
          }
        }
```

## Configuration

If using the default `gql` backend for graphql,
default options can be passed to the underlying HTTP transport in the "gql" block at the top level for a test.
The GraphQL configuration schema supports these options:

```yaml
gql:
  headers:
    string: string              # Default headers for all requests
```

For example, to make a query with an authorization header for all requests, not just for a single stage
like in [the example below](#query-with-headers):

```yaml
---

test_name: Query with global authorization header

gql:
  headers:
    Authorization: "Bearer test-token"

stages:
  - name: Query with authorization header
    graphql_request:
      ...
```

## Requests

### Basic Query

```yaml
stages:
  - name: Get user by ID
    graphql_request:
      url: "{graphql_server_url}/graphql"
      query: |
        query GetUser($id: ID!) {
          user(id: $id) {
            id
            name
            email
          }
        }
      variables:
        id: "1"
```

### Query with Variables

Use variables for dynamic data. Variables support standard Tavern formatting:

```yaml
stages:
  - name: Create user with test variables
    graphql_request:
      url: "{graphql_server_url}/graphql"
      query: |
        mutation CreateUser($name: String!, $email: String!) {
          createUser(name: $name, email: $email) {
            id
            name
            email
          }
        }
      variables:
        name: "{user_name}"        # Formatting works in variables
        email: "{user_email}"
```

### Query with Headers

```yaml
stages:
  - name: Authenticated query
    graphql_request:
      url: "{graphql_server_url}/graphql"
      headers:
        Authorization: "Bearer {auth_token}"
        Content-Type: "application/json"  # Automatically added if not present
      query: |
        query GetUserData {
          me {
            id
            name
          }
        }
```

### Query with Operation Name

```yaml
stages:
  - name: Named operation
    graphql_request:
      url: "{graphql_server_url}/graphql"
      query: |
        query GetUserProfile($id: ID!) {
          user(id: $id) {
            id
            name
            profile {
              bio
              avatar
            }
          }
        }
      operation_name: GetUserProfile      # Optional operation name
      variables:
        id: "1"
```

### Multiple Operations in One Query

```yaml
stages:
  - name: Multiple operations
    graphql_request:
      url: "{graphql_server_url}/graphql"
      query: |
        query GetUser($id: ID!) {
          user(id: $id) {
            id
            name
          }
        }

        mutation UpdateUser($id: ID!, $name: String!) {
          updateUser(id: $id, name: $name) {
            id
            name
          }
        }
      operation_name: GetUser           # Specify which operation to execute
      variables:
        id: "1"
```

> **Note**: While `operation_name` isn't required for most use cases, it becomes essential when you're using the
> `!include` tag to import an external GraphQL file that contains multiple operations. In such cases, `operation_name`
> helps identify which specific operation you want to execute from the included file.

### File Uploads

GraphQL file uploads are handled through variables using the `Upload` scalar type. You need to:

1. Define a GraphQL mutation with an `Upload` parameter
2. Use the `files:` block in your test to specify which file to upload
3. The variable name in `files:` must match the parameter name in your mutation

```yaml
stages:
  - name: Upload a file
    graphql_request:
      url: "{graphql_server_url}/graphql"
      query: |
        mutation UploadFile($file: Upload!, $title: String!) {
          uploadFile(file: $file, title: $title) {
            id
            filename
            url
          }
        }
      variables:
        title: "My File"
      files:
        file: path/to/local/file.txt
    graphql_response:
      data:
        uploadFile:
          id: !anyint
          filename: "file.txt"
```

You can upload multiple files by specifying them in the `files:` block:

```yaml
stages:
  - name: Upload multiple files
    graphql_request:
      url: "{graphql_server_url}/graphql"
      query: |
        mutation UploadFiles($file1: Upload!, $file2: Upload!) {
          uploadMultiple(file1: $file1, file2: $file2) {
            id
            files {
              filename
              url
            }
          }
        }
      variables:
        # Note: variables here should not contain the file data
      files:
        file1: path/to/first/file.txt
        file2: path/to/second/file.txt
    graphql_response:
      json:
        data:
          uploadMultiple:
            id: !anyint
            files:
              - filename: "file.txt"
              - filename: "file.txt"
```

You can also use the **long form** to specify the `content_type` of the file:

```yaml
stages:
  - name: Upload file with custom content type
    graphql_request:
      url: "{graphql_server_url}/graphql"
      query: |
        mutation UploadFile($myFileName: Upload!, $title: String!) {
          uploadFile(file: $myFileName, title: $title) {
            id
            filename
          }
        }
      variables:
        title: "Custom File"
      files:
        myFileName:
          file_path: path/to/file.txt
          content_type: text/plain
    graphql_response:
      data:
        uploadFile:
          id: !anyint
          filename: "file.txt"
```

## Responses

GraphQL responses follow the standard GraphQL format with `data` and/or `errors` at the top level:

### Successful Response

```yaml
stages:
  - name: Query user
    graphql_request:
      url: "{graphql_server_url}/graphql"
      query: |
        query GetUser($id: ID!) {
          user(id: $id) {
            id
            name
          }
        }
      variables:
        id: "1"
    graphql_response:
      json:
        data:
          user:
            id: "1"
            name: "John Doe"
```

### Response with Errors

```yaml
stages:
  - name: Invalid query
    graphql_request:
      url: "{graphql_server_url}/graphql"
      query: |
        query InvalidQuery {
          invalidField {
            id
          }
        }
    graphql_response:
      json:
        errors:
          - message: "Cannot query field 'invalidField' on type 'Query'."
```

## Strictness

As described in the [strict key checking](./core_concepts/types.md#strict-key-checking) section in the basics, GraphQL responses
can use the `strict` key to check that the response contains all or some of the expected keys. See that section
for more details. The `json` strictness setting is reused for the `data` key in GraphQL responses.

## Error Handling

The GraphQL plugin provides specific error handling for common GraphQL scenarios:

### Response Structure Validation

Tavern automatically validates that GraphQL responses have the correct structure:

- Must contain only `data` or `errors` at the top level
- Cannot have other top-level keys

### Status Code Handling

GraphQL responses should always return HTTP 200 status codes, even for:

- Validation errors
- Business logic errors
- Missing required fields

Non-200 status codes indicate HTTP-level problems (authentication, network issues, etc.).

## Limitations

### Current Limitations

- **Query Formatting**: Cannot use `{variable}` formatting in GraphQL queries themselves - use GraphQL variables instead
- **Streaming**: No support for streaming responses (defer/stream directives)

### Future Plans

- Improved error message formatting
- Extensions (unsupported by [gql](https://github.com/graphql-python/gql) so we'd need to implement our own)
- 'Partial responses' (errors and data in the response)
