package test.unit;

import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE;
import static org.assertj.core.api.Assertions.catchThrowableOfType;
import static org.assertj.core.api.BDDAssertions.then;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;

import javax.ws.rs.core.Response;

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import io.smallrye.graphql.client.typesafe.api.GraphQlClientApi;
import io.smallrye.graphql.client.typesafe.api.GraphQlClientException;

class ScalarBehavior {
    private final GraphQlClientFixture fixture = new GraphQlClientFixture();

    @GraphQlClientApi
    interface BoolApi {
        boolean bool();
    }

    @GraphQlClientApi
    interface BooleanApi {
        Boolean bool();
    }

    @Nested
    class BooleanBehavior {
        @Test
        void shouldCallBoolQuery() {
            fixture.returnsData("'bool':true");
            BoolApi api = fixture.builder().build(BoolApi.class);

            boolean bool = api.bool();

            then(fixture.query()).isEqualTo("bool");
            then(bool).isTrue();
        }

        @Test
        void shouldFailToAssignNullToBool() {
            fixture.returnsData("'bool':null");
            BoolApi api = fixture.builder().build(BoolApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::bool, GraphQlClientException.class);

            then(thrown).hasMessage("invalid boolean value for " + BoolApi.class.getName() + "#bool: null");
        }

        @Test
        void shouldFailToAssignStringToBool() {
            fixture.returnsData("'bool':'xxx'");
            BoolApi api = fixture.builder().build(BoolApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::bool, GraphQlClientException.class);

            then(thrown).hasMessage("invalid boolean value for " + BoolApi.class.getName() + "#bool: \"xxx\"");
        }

        @Test
        void shouldFailToAssignNumberToBool() {
            fixture.returnsData("'bool':123");
            BoolApi api = fixture.builder().build(BoolApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::bool, GraphQlClientException.class);

            then(thrown).hasMessage("invalid boolean value for " + BoolApi.class.getName() + "#bool: 123");
        }

        @Test
        void shouldFailToAssignListToBool() {
            fixture.returnsData("'bool':[123]");
            BoolApi api = fixture.builder().build(BoolApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::bool, GraphQlClientException.class);

            then(thrown).hasMessage("invalid boolean value for " + BoolApi.class.getName() + "#bool: [123]");
        }

        @Test
        void shouldFailToAssignObjectToBool() {
            fixture.returnsData("'bool':{'foo':'bar'}");
            BoolApi api = fixture.builder().build(BoolApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::bool, GraphQlClientException.class);

            then(thrown).hasMessage("invalid boolean value for " + BoolApi.class.getName() + "#bool: {\"foo\":\"bar\"}");
        }

        @Test
        void shouldCallBooleanQuery() {
            fixture.returnsData("'bool':true");
            BooleanApi api = fixture.builder().build(BooleanApi.class);

            Boolean bool = api.bool();

            then(fixture.query()).isEqualTo("bool");
            then(bool).isTrue();
        }
    }

    @GraphQlClientApi
    interface ByteApi {
        Byte code();
    }

    @GraphQlClientApi
    interface PrimitiveByteApi {
        byte code();
    }

    @Nested
    class ByteBehavior {
        @Test
        void shouldCallByteQuery() {
            fixture.returnsData("'code':5");
            ByteApi api = fixture.builder().build(ByteApi.class);

            Byte code = api.code();

            then(fixture.query()).isEqualTo("code");
            then(code).isEqualTo((byte) 5);
        }

        @Test
        void shouldCallPrimitiveByteQuery() {
            fixture.returnsData("'code':5");
            PrimitiveByteApi api = fixture.builder().build(PrimitiveByteApi.class);

            byte code = api.code();

            then(fixture.query()).isEqualTo("code");
            then(code).isEqualTo((byte) 5);
        }

        @Test
        void shouldFailByteFromTooBigNumberQuery() {
            int tooBig = (int) Byte.MAX_VALUE + 1;
            fixture.returnsData("'code':" + tooBig);
            ByteApi api = fixture.builder().build(ByteApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::code, GraphQlClientException.class);

            then(thrown).hasMessage("invalid java.lang.Byte value for " + ByteApi.class.getName() + "#code: " + tooBig);
        }

        @Test
        void shouldFailByteFromNegativeNumberQuery() {
            int tooSmall = (int) Byte.MIN_VALUE - 1;
            fixture.returnsData("'code':" + tooSmall);
            ByteApi api = fixture.builder().build(ByteApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::code, GraphQlClientException.class);

            then(thrown).hasMessage("invalid java.lang.Byte value for " + ByteApi.class.getName() + "#code: " + tooSmall);
        }
    }

    @GraphQlClientApi
    interface CharacterApi {
        Character code();
    }

    @GraphQlClientApi
    interface PrimitiveCharApi {
        char code();
    }

    @Nested
    class CharacterBehavior {
        @Test
        void shouldCallCharacterFromStringQuery() {
            fixture.returnsData("'code':'a'");
            CharacterApi api = fixture.builder().build(CharacterApi.class);

            Character c = api.code();

            then(fixture.query()).isEqualTo("code");
            then(c).isEqualTo('a');
        }

        @Test
        void shouldFailCharacterFromStringQueryWithMoreThanOneCharacter() {
            fixture.returnsData("'code':'ab'");
            CharacterApi api = fixture.builder().build(CharacterApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::code, GraphQlClientException.class);

            then(thrown).hasMessage("invalid java.lang.Character value for " + CharacterApi.class.getName() + "#code: \"ab\"");
        }

        @Test
        void shouldCallCharacterFromNumberQuery() {
            fixture.returnsData("'code':97");
            CharacterApi api = fixture.builder().build(CharacterApi.class);

            Character c = api.code();

            then(fixture.query()).isEqualTo("code");
            then(c).isEqualTo('a');
        }

        @Test
        void shouldFailCharacterFromTooBigNumberQuery() {
            int tooBig = (int) Character.MAX_VALUE + 1;
            fixture.returnsData("'code':" + tooBig);
            CharacterApi api = fixture.builder().build(CharacterApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::code, GraphQlClientException.class);

            then(thrown)
                    .hasMessage("invalid java.lang.Character value for " + CharacterApi.class.getName() + "#code: " + tooBig);
        }

        @Test
        void shouldFailCharacterFromNegativeNumberQuery() {
            fixture.returnsData("'code':-15");
            CharacterApi api = fixture.builder().build(CharacterApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::code, GraphQlClientException.class);

            then(thrown).hasMessage("invalid java.lang.Character value for " + CharacterApi.class.getName() + "#code: -15");
        }

        @Test
        void shouldCallPrimitiveCharQuery() {
            fixture.returnsData("'code':'a'");
            PrimitiveCharApi api = fixture.builder().build(PrimitiveCharApi.class);

            char c = api.code();

            then(fixture.query()).isEqualTo("code");
            then(c).isEqualTo('a');
        }

        @Test
        void shouldFailPrimitiveCharQueryWithMoreThanOneCharacter() {
            fixture.returnsData("'code':'ab'");
            PrimitiveCharApi api = fixture.builder().build(PrimitiveCharApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::code, GraphQlClientException.class);

            then(thrown).hasMessage("invalid char value for " + PrimitiveCharApi.class.getName() + "#code: \"ab\"");
        }
    }

    @GraphQlClientApi
    interface ShortApi {
        Short code();
    }

    @GraphQlClientApi
    interface PrimitiveShortApi {
        short code();
    }

    @Nested
    class ShortBehavior {
        @Test
        void shouldCallShortQuery() {
            fixture.returnsData("'code':5");
            ShortApi api = fixture.builder().build(ShortApi.class);

            Short code = api.code();

            then(fixture.query()).isEqualTo("code");
            then(code).isEqualTo((short) 5);
        }

        @Test
        void shouldFailToCallTooSmallShortQuery() {
            int tooSmall = (int) Short.MIN_VALUE - 1;
            fixture.returnsData("'code':" + tooSmall);
            ShortApi api = fixture.builder().build(ShortApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::code, GraphQlClientException.class);

            then(thrown).hasMessage("invalid java.lang.Short value for " + ShortApi.class.getName() + "#code: " + tooSmall);
        }

        @Test
        void shouldFailToCallTooBigShortQuery() {
            int tooBig = (int) Short.MAX_VALUE + 1;
            fixture.returnsData("'code':" + tooBig);
            ShortApi api = fixture.builder().build(ShortApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::code, GraphQlClientException.class);

            then(thrown).hasMessage("invalid java.lang.Short value for " + ShortApi.class.getName() + "#code: " + tooBig);
        }

        @Test
        void shouldCallPrimitiveShortQuery() {
            fixture.returnsData("'code':5");
            PrimitiveShortApi api = fixture.builder().build(PrimitiveShortApi.class);

            short code = api.code();

            then(fixture.query()).isEqualTo("code");
            then(code).isEqualTo((short) 5);
        }
    }

    @GraphQlClientApi
    interface IntegerApi {
        Integer code();
    }

    @GraphQlClientApi
    interface IntApi {
        int code();
    }

    @Nested
    class IntegerBehavior {
        @Test
        void shouldCallIntegerQuery() {
            fixture.returnsData("'code':5");
            IntegerApi api = fixture.builder().build(IntegerApi.class);

            Integer code = api.code();

            then(fixture.query()).isEqualTo("code");
            then(code).isEqualTo(5);
        }

        @Test
        void shouldFailToCallDoubleQuery() {
            double number = 123.456d;
            fixture.returnsData("'code':" + number);
            IntegerApi api = fixture.builder().build(IntegerApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::code, GraphQlClientException.class);

            then(thrown).hasMessage("invalid java.lang.Integer value for " + IntegerApi.class.getName() + "#code: " + number);
        }

        @Test
        void shouldFailToCallTooSmallIntegerQuery() {
            long tooSmall = (long) Integer.MIN_VALUE - 1;
            fixture.returnsData("'code':" + tooSmall);
            IntegerApi api = fixture.builder().build(IntegerApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::code, GraphQlClientException.class);

            then(thrown).hasMessage("invalid java.lang.Integer value for " + IntegerApi.class.getName() + "#code: " + tooSmall);
        }

        @Test
        void shouldFailToCallTooBigIntegerQuery() {
            long tooBig = (long) Integer.MAX_VALUE + 1;
            fixture.returnsData("'code':" + tooBig);
            IntegerApi api = fixture.builder().build(IntegerApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::code, GraphQlClientException.class);

            then(thrown).hasMessage("invalid java.lang.Integer value for " + IntegerApi.class.getName() + "#code: " + tooBig);
        }

        @Test
        void shouldCallIntQuery() {
            fixture.returnsData("'code':5");
            IntApi api = fixture.builder().build(IntApi.class);

            int code = api.code();

            then(fixture.query()).isEqualTo("code");
            then(code).isEqualTo(5);
        }
    }

    @GraphQlClientApi
    interface LongApi {
        Long code();
    }

    @GraphQlClientApi
    interface PrimitiveLongApi {
        long code();
    }

    @Nested
    class LongBehavior {
        @Test
        void shouldCallLongQuery() {
            fixture.returnsData("'code':5");
            LongApi api = fixture.builder().build(LongApi.class);

            Long code = api.code();

            then(fixture.query()).isEqualTo("code");
            then(code).isEqualTo(5L);
        }

        @Test
        void shouldFailToCallTooSmallLongQuery() {
            String tooSmall = "-9223372036854775809";
            fixture.returnsData("'code':" + tooSmall);
            LongApi api = fixture.builder().build(LongApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::code, GraphQlClientException.class);

            then(thrown).hasMessage("invalid java.lang.Long value for " + LongApi.class.getName() + "#code: " + tooSmall);
        }

        @Test
        void shouldFailToCallTooBigLongQuery() {
            String tooBig = "9223372036854775808";
            fixture.returnsData("'code':" + tooBig);
            LongApi api = fixture.builder().build(LongApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::code, GraphQlClientException.class);

            then(thrown).hasMessage("invalid java.lang.Long value for " + LongApi.class.getName() + "#code: " + tooBig);
        }

        @Test
        void shouldCallPrimitiveLongQuery() {
            fixture.returnsData("'code':5");
            PrimitiveLongApi api = fixture.builder().build(PrimitiveLongApi.class);

            long code = api.code();

            then(fixture.query()).isEqualTo("code");
            then(code).isEqualTo(5L);
        }
    }

    @GraphQlClientApi
    interface FloatApi {
        Float number();
    }

    @GraphQlClientApi
    interface PrimitiveFloatApi {
        float number();
    }

    @Nested
    class FloatBehavior {
        @Test
        void shouldCallFloatQuery() {
            fixture.returnsData("'number':123.456");
            FloatApi api = fixture.builder().build(FloatApi.class);

            Float number = api.number();

            then(fixture.query()).isEqualTo("number");
            then(number).isEqualTo(123.456f);
        }

        @Test
        void shouldCallPrimitiveFloatQuery() {
            fixture.returnsData("'number':123.456");
            PrimitiveFloatApi api = fixture.builder().build(PrimitiveFloatApi.class);

            float number = api.number();

            then(fixture.query()).isEqualTo("number");
            then(number).isEqualTo(123.456f);
        }
    }

    @GraphQlClientApi
    interface DoubleApi {
        Double number();
    }

    @GraphQlClientApi
    interface PrimitiveDoubleApi {
        double number();
    }

    @Nested
    class DoubleBehavior {
        @Test
        void shouldCallDoubleQuery() {
            fixture.returnsData("'number':123.456");
            DoubleApi api = fixture.builder().build(DoubleApi.class);

            Double number = api.number();

            then(fixture.query()).isEqualTo("number");
            then(number).isEqualTo(123.456D);
        }

        @Test
        void shouldCallPrimitiveDoubleQuery() {
            fixture.returnsData("'number':123.456");
            PrimitiveDoubleApi api = fixture.builder().build(PrimitiveDoubleApi.class);

            double number = api.number();

            then(fixture.query()).isEqualTo("number");
            then(number).isEqualTo(123.456D);
        }
    }

    @GraphQlClientApi
    interface BigIntegerApi {
        BigInteger number();
    }

    @Nested
    class BigIntegerBehavior {
        @Test
        void shouldCallReallyLongIntegerQuery() {
            String reallyLongInteger = "12345678901234567890123456789012345678901234567890123456789012345678901234567890";
            fixture.returnsData("'number':" + reallyLongInteger);
            BigIntegerApi api = fixture.builder().build(BigIntegerApi.class);

            BigInteger number = api.number();

            then(fixture.query()).isEqualTo("number");
            then(number).isEqualTo(reallyLongInteger);
        }

        @Test
        void shouldCallNotSoLongIntegerQuery() {
            String notSoLongInteger = "123456";
            fixture.returnsData("'number':" + notSoLongInteger);
            BigIntegerApi api = fixture.builder().build(BigIntegerApi.class);

            BigInteger number = api.number();

            then(fixture.query()).isEqualTo("number");
            then(number).isEqualTo(notSoLongInteger);
        }
    }

    @GraphQlClientApi
    interface BigDecimalApi {
        BigDecimal number();
    }

    @Nested
    class BigDecimalBehavior {
        @Test
        void shouldCallReallyLongDecimalQuery() {
            String reallyLongDecimal = "123.45678901234567890123456789012345678901234567890123456789012345678901234567890";
            fixture.returnsData("'number':" + reallyLongDecimal);
            BigDecimalApi api = fixture.builder().build(BigDecimalApi.class);

            BigDecimal number = api.number();

            then(fixture.query()).isEqualTo("number");
            then(number).isEqualTo(reallyLongDecimal);
        }

        @Test
        void shouldCallNotSoLongDecimalQuery() {
            String notSoLongDecimal = "123.456";
            fixture.returnsData("'number':" + notSoLongDecimal);
            BigDecimalApi api = fixture.builder().build(BigDecimalApi.class);

            BigDecimal number = api.number();

            then(fixture.query()).isEqualTo("number");
            then(number).isEqualTo(notSoLongDecimal);
        }
    }

    @GraphQlClientApi
    interface StringApi {
        String greeting();
    }

    @GraphQlClientApi
    interface ScalarWithValueOfApi {
        Integer foo();
    }

    @GraphQlClientApi
    interface ScalarWithParseApi {
        LocalDate now();
    }

    @GraphQlClientApi
    interface NonScalarWithStringConstructorApi {
        NonScalarWithStringConstructor foo();
    }

    public static class NonScalarWithStringConstructor {
        private final String value;

        @SuppressWarnings("unused")
        public NonScalarWithStringConstructor() {
            this(null);
        }

        public NonScalarWithStringConstructor(String value) {
            this.value = value;
        }
    }

    @GraphQlClientApi
    interface FailingScalarApi {
        @SuppressWarnings("UnusedReturnValue")
        FailingScalar foo();
    }

    public static class FailingScalar {
        @SuppressWarnings("unused")
        public static FailingScalar valueOf(String text) {
            return new FailingScalar(text);
        }

        @SuppressWarnings("unused")
        private final String text;

        public FailingScalar(String text) {
            throw new RuntimeException("dummy exception: " + text);
        }
    }

    @GraphQlClientApi
    interface ScalarWithStringConstructorMethodApi {
        ScalarWithStringConstructorMethod foo();
    }

    @SuppressWarnings("unused")
    public static class ScalarWithStringConstructorMethod {
        public static ScalarWithStringConstructorMethod other(String text) {
            return null;
        }

        public static ScalarWithStringConstructorMethod valueOf() {
            return null;
        }

        public static ScalarWithStringConstructorMethod valueOf(int i) {
            return null;
        }

        public static void valueOf(String text) {
        }

        public static ScalarWithStringConstructorMethod parse(String text) {
            ScalarWithStringConstructorMethod result = new ScalarWithStringConstructorMethod();
            result.text = text;
            return result;
        }

        String text;
    }

    @GraphQlClientApi
    interface ScalarWithOfConstructorMethodApi {
        ScalarWithOfConstructorMethod foo();
    }

    public static class ScalarWithOfConstructorMethod {
        @SuppressWarnings("unused")
        public static ScalarWithOfConstructorMethod of(String text) {
            return new ScalarWithOfConstructorMethod("x-" + text);
        }

        public ScalarWithOfConstructorMethod(String text) {
            this.text = text;
        }

        String text;
    }

    @Nested
    class StringBehavior {
        @Test
        void shouldCallStringQuery() {
            fixture.returnsData("'greeting':'dummy-greeting'");
            StringApi api = fixture.builder().build(StringApi.class);

            String greeting = api.greeting();

            then(fixture.query()).isEqualTo("greeting");
            then(greeting).isEqualTo("dummy-greeting");
        }

        @Test
        void shouldFailStringQueryNotFound() {
            fixture.returns(Response.serverError().type(TEXT_PLAIN_TYPE).entity("failed").build());
            StringApi api = fixture.builder().build(StringApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::greeting, GraphQlClientException.class);

            then(thrown).hasMessage("expected successful status code but got 500 Internal Server Error:\nfailed");
        }

        @Test
        void shouldFailOnQueryError() {
            fixture.returns(Response.ok("{\"errors\":[{\"message\":\"failed\"}]}").build());
            StringApi api = fixture.builder().build(StringApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::greeting, GraphQlClientException.class);

            then(thrown).hasMessage("errors from service: [{\"message\":\"failed\"}]:\n" +
                    "  {\"query\":\"query { greeting }\"}");
        }

        @Test
        void shouldFailOnMissingQueryResponse() {
            fixture.returnsData("");
            StringApi api = fixture.builder().build(StringApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::greeting, GraphQlClientException.class);

            then(thrown).hasMessage("no data for 'greeting':\n  {}");
        }

        @Test
        void shouldCallScalarWithValueOfQuery() {
            fixture.returnsData("'foo':123456");
            ScalarWithValueOfApi api = fixture.builder().build(ScalarWithValueOfApi.class);

            Integer value = api.foo();

            then(fixture.query()).isEqualTo("foo");
            then(value).isEqualTo(123456);
        }

        @Test
        void shouldCallScalarWithParseQuery() {
            LocalDate now = LocalDate.now();
            fixture.returnsData("'now':'" + now + "'");
            ScalarWithParseApi api = fixture.builder().build(ScalarWithParseApi.class);

            LocalDate value = api.now();

            then(fixture.query()).isEqualTo("now");
            then(value).isEqualTo(now);
        }

        @Test
        void shouldCallNonScalarWithStringConstructorApiQuery() {
            fixture.returnsData("'foo':{'value':'1234'}");
            NonScalarWithStringConstructorApi api = fixture.builder().build(NonScalarWithStringConstructorApi.class);

            NonScalarWithStringConstructor result = api.foo();

            then(fixture.query()).isEqualTo("foo {value}");
            then(result.value).isEqualTo("1234");
        }

        @Test
        void shouldFailToCreateFailingScalar() {
            fixture.returnsData("'foo':'a'");
            FailingScalarApi api = fixture.builder().build(FailingScalarApi.class);

            GraphQlClientException thrown = catchThrowableOfType(api::foo, GraphQlClientException.class);

            then(thrown).hasMessage("can't create scalar " + FailingScalar.class.getName() + " value " +
                    "for " + FailingScalarApi.class.getName() + "#foo");
        }

        @Test
        void shouldCallScalarWithStringConstructorMethodQuery() {
            fixture.returnsData("'foo':'bar'");
            ScalarWithStringConstructorMethodApi api = fixture.builder().build(ScalarWithStringConstructorMethodApi.class);

            ScalarWithStringConstructorMethod value = api.foo();

            then(fixture.query()).isEqualTo("foo");
            then(value.text).isEqualTo("bar");
        }

        @Test
        void shouldCallScalarWithOfConstructorMethodQuery() {
            fixture.returnsData("'foo':'bar'");
            ScalarWithOfConstructorMethodApi api = fixture.builder().build(ScalarWithOfConstructorMethodApi.class);

            ScalarWithOfConstructorMethod value = api.foo();

            then(fixture.query()).isEqualTo("foo");
            then(value.text).isEqualTo("x-bar");
        }
    }

    interface StringGettersApi {
        String getGreeting();

        String get();

        String getG();

        String gets();

        String getting();
    }

    @Test
    void shouldCallStringGetterQuery() {
        fixture.returnsData("'greeting':'foo'");
        StringGettersApi api = fixture.builder().build(StringGettersApi.class);

        String value = api.getGreeting();

        then(fixture.query()).isEqualTo("greeting");
        then(value).isEqualTo("foo");
    }

    @Test
    void shouldCallJustGetQuery() {
        fixture.returnsData("'get':'foo'");
        StringGettersApi api = fixture.builder().build(StringGettersApi.class);

        String value = api.get();

        then(fixture.query()).isEqualTo("get");
        then(value).isEqualTo("foo");
    }

    @Test
    void shouldCallOneCharGetterQuery() {
        fixture.returnsData("'g':'foo'");
        StringGettersApi api = fixture.builder().build(StringGettersApi.class);

        String value = api.getG();

        then(fixture.query()).isEqualTo("g");
        then(value).isEqualTo("foo");
    }

    @Test
    void shouldCallGetAndOneLowerCharQuery() {
        fixture.returnsData("'gets':'foo'");
        StringGettersApi api = fixture.builder().build(StringGettersApi.class);

        String value = api.gets();

        then(fixture.query()).isEqualTo("gets");
        then(value).isEqualTo("foo");
    }

    @Test
    void shouldCallGetAndLowerCharsQuery() {
        fixture.returnsData("'getting':'foo'");
        StringGettersApi api = fixture.builder().build(StringGettersApi.class);

        String value = api.getting();

        then(fixture.query()).isEqualTo("getting");
        then(value).isEqualTo("foo");
    }
}