Repository: smallrye/smallrye-graphql
Issue: Non-Ascii-Characters sent to server

When we send a non-ASCII character to the GraphQL server, the encoding is being mixed up.

I've looked at the TCK and tried to add a `ö` to the `ScalarTestApi` string `123456789`, but when I add it to the `output.json` it doesn't accept the json.

Changed files:
client/implementation/src/test/java/test/unit/HeaderBehavior.java
client/implementation/src/test/java/test/unit/GraphQlClientFixture.java
client/implementation/src/main/java/io/smallrye/graphql/client/typesafe/impl/GraphQlClientProxy.java

diff --git a/client/implementation/src/main/java/io/smallrye/graphql/client/typesafe/impl/GraphQlClientProxy.java b/client/implementation/src/main/java/io/smallrye/graphql/client/typesafe/impl/GraphQlClientProxy.java
index df002a75..384b73b0 100644
--- a/client/implementation/src/main/java/io/smallrye/graphql/client/typesafe/impl/GraphQlClientProxy.java
+++ b/client/implementation/src/main/java/io/smallrye/graphql/client/typesafe/impl/GraphQlClientProxy.java
@@ -2,6 +2,7 @@ package io.smallrye.graphql.client.typesafe.impl;
 
 import static java.util.stream.Collectors.joining;
 import static javax.json.JsonValue.ValueType.ARRAY;
+import static javax.ws.rs.client.Entity.entity;
 import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
 import static javax.ws.rs.core.Response.Status.Family.SUCCESSFUL;
 
@@ -14,8 +15,8 @@ import javax.json.JsonObject;
 import javax.json.JsonObjectBuilder;
 import javax.json.JsonReaderFactory;
 import javax.json.JsonValue;
-import javax.ws.rs.client.Entity;
 import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.StatusType;
@@ -31,6 +32,7 @@ import io.smallrye.graphql.client.typesafe.impl.reflection.TypeInfo;
 
 class GraphQlClientProxy {
     private static final Logger log = LoggerFactory.getLogger(GraphQlClientProxy.class);
+    private static final MediaType APPLICATION_JSON_UTF8 = APPLICATION_JSON_TYPE.withCharset("utf-8");
 
     private static final JsonBuilderFactory jsonObjectFactory = Json.createBuilderFactory(null);
     private static final JsonReaderFactory jsonReaderFactory = Json.createReaderFactory(null);
@@ -104,9 +106,9 @@ class GraphQlClientProxy {
 
     private String post(String request, MultivaluedMap<String, Object> headers) {
         Response response = target
-                .request(APPLICATION_JSON_TYPE)
+                .request(APPLICATION_JSON_UTF8)
                 .headers(headers)
-                .post(Entity.json(request));
+                .post(entity(request, APPLICATION_JSON_UTF8));
         StatusType status = response.getStatusInfo();
         if (status.getFamily() != SUCCESSFUL)
             throw new GraphQlClientException("expected successful status code but got " +
diff --git a/client/implementation/src/test/java/test/unit/GraphQlClientFixture.java b/client/implementation/src/test/java/test/unit/GraphQlClientFixture.java
index 434cb81f..26544db1 100644
--- a/client/implementation/src/test/java/test/unit/GraphQlClientFixture.java
+++ b/client/implementation/src/test/java/test/unit/GraphQlClientFixture.java
@@ -1,6 +1,5 @@
 package test.unit;
 
-import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
 import static org.assertj.core.api.BDDAssertions.then;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.BDDMockito.given;
@@ -15,6 +14,7 @@ import javax.ws.rs.client.Client;
 import javax.ws.rs.client.Entity;
 import javax.ws.rs.client.Invocation;
 import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedHashMap;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
@@ -27,14 +27,14 @@ import io.smallrye.graphql.client.typesafe.impl.GraphQlClientBuilderImpl;
 
 class GraphQlClientFixture {
     private final Client mockClient = mock(Client.class);
+    private final WebTarget mockWebTarget = mock(WebTarget.class);
     private final Invocation.Builder mockInvocationBuilder = mock(Invocation.Builder.class);
     private Response response;
+    private Entity<String> entitySent;
 
     GraphQlClientFixture() {
-        WebTarget mockWebTarget = mock(WebTarget.class);
-
         given(mockClient.target(any(URI.class))).willReturn(mockWebTarget);
-        given(mockWebTarget.request(APPLICATION_JSON_TYPE)).willReturn(mockInvocationBuilder);
+        given(mockWebTarget.request(any(MediaType.class))).willReturn(mockInvocationBuilder);
         given(mockInvocationBuilder.headers(any())).willReturn(mockInvocationBuilder);
         given(mockInvocationBuilder.post(any())).will(i -> response);
     }
@@ -66,18 +66,21 @@ class GraphQlClientFixture {
     }
 
     String rawQuery() {
-        return queryBody(captureRequestEntity(), "query");
+        return queryBody(entitySent().getEntity(), "query");
     }
 
     String mutation() {
-        return queryBody(captureRequestEntity(), "mutation").replace('\\"', '\\'');
+        return queryBody(entitySent().getEntity(), "mutation").replace('\\"', '\\'');
     }
 
-    private String captureRequestEntity() {
-        @SuppressWarnings("unchecked")
-        ArgumentCaptor<Entity<String>> captor = ArgumentCaptor.forClass(Entity.class);
-        BDDMockito.then(mockInvocationBuilder).should().post(captor.capture());
-        return captor.getValue().getEntity();
+    private Entity<String> entitySent() {
+        if (entitySent == null) {
+            @SuppressWarnings("unchecked")
+            ArgumentCaptor<Entity<String>> captor = ArgumentCaptor.forClass(Entity.class);
+            BDDMockito.then(mockInvocationBuilder).should().post(captor.capture());
+            entitySent = captor.getValue();
+        }
+        return entitySent;
     }
 
     private String queryBody(String response, String operation) {
@@ -100,10 +103,23 @@ class GraphQlClientFixture {
     }
 
     MultivaluedMap<String, Object> sentHeaders() {
+        MultivaluedMap<String, Object> map = captureExplicitHeaders();
+        map.putSingle("Accept", captureAcceptHeader());
+        map.putSingle("Content-Type", entitySent().getMediaType());
+        return map;
+    }
+
+    private MultivaluedMap<String, Object> captureExplicitHeaders() {
         @SuppressWarnings("unchecked")
         ArgumentCaptor<MultivaluedMap<String, Object>> captor = ArgumentCaptor.forClass(MultivaluedMap.class);
         BDDMockito.then(mockInvocationBuilder).should().headers(captor.capture());
         MultivaluedMap<String, Object> map = captor.getValue();
         return (map == null) ? new MultivaluedHashMap<>() : map;
     }
+
+    private MediaType captureAcceptHeader() {
+        ArgumentCaptor<MediaType> captor = ArgumentCaptor.forClass(MediaType.class);
+        BDDMockito.then(mockWebTarget).should().request(captor.capture());
+        return captor.getValue();
+    }
 }
diff --git a/client/implementation/src/test/java/test/unit/HeaderBehavior.java b/client/implementation/src/test/java/test/unit/HeaderBehavior.java
index be8da39e..2dc58d75 100644
--- a/client/implementation/src/test/java/test/unit/HeaderBehavior.java
+++ b/client/implementation/src/test/java/test/unit/HeaderBehavior.java
@@ -561,4 +561,21 @@ public class HeaderBehavior {
         then(fixture.sentHeader("H5")).isEqualTo("V5");
         then(fixture.sentHeader("overwrite")).isEqualTo("sub");
     }
+
+    @GraphQlClientApi
+    interface SimpleApi {
+        String greeting(String target);
+    }
+
+    @Test
+    public void shouldAddCharsetRequestAndResponseHeaders() {
+        fixture.returnsData("'greeting':'dummy-greeting'");
+        SimpleApi api = fixture.builder().build(SimpleApi.class);
+
+        api.greeting("foo");
+
+        then(fixture.query()).isEqualTo("greeting(target: 'foo')");
+        then(fixture.sentHeader("Content-Type")).hasToString("application/json;charset=utf-8");
+        then(fixture.sentHeader("Accept")).hasToString("application/json;charset=utf-8");
+    }
 }