diff --git a/ezyhttp-client/pom.xml b/ezyhttp-client/pom.xml index 0aa02aa..7d4fe77 100644 --- a/ezyhttp-client/pom.xml +++ b/ezyhttp-client/pom.xml @@ -5,7 +5,7 @@ com.tvd12 ezyhttp - 1.5.3 + 1.5.4 ezyhttp-client diff --git a/ezyhttp-core/pom.xml b/ezyhttp-core/pom.xml index c6659cb..ebd46f5 100644 --- a/ezyhttp-core/pom.xml +++ b/ezyhttp-core/pom.xml @@ -5,7 +5,7 @@ com.tvd12 ezyhttp - 1.5.3 + 1.5.4 ezyhttp-core diff --git a/ezyhttp-server-boot/pom.xml b/ezyhttp-server-boot/pom.xml index d9791c5..8829c28 100644 --- a/ezyhttp-server-boot/pom.xml +++ b/ezyhttp-server-boot/pom.xml @@ -5,7 +5,7 @@ com.tvd12 ezyhttp - 1.5.3 + 1.5.4 ezyhttp-server-boot diff --git a/ezyhttp-server-core/pom.xml b/ezyhttp-server-core/pom.xml index ef6f578..cb8cf3d 100644 --- a/ezyhttp-server-core/pom.xml +++ b/ezyhttp-server-core/pom.xml @@ -5,7 +5,7 @@ com.tvd12 ezyhttp - 1.5.3 + 1.5.4 ezyhttp-server-core diff --git a/ezyhttp-server-graphql/pom.xml b/ezyhttp-server-graphql/pom.xml index a92e7d7..40e5a6a 100644 --- a/ezyhttp-server-graphql/pom.xml +++ b/ezyhttp-server-graphql/pom.xml @@ -6,7 +6,7 @@ com.tvd12 ezyhttp - 1.5.3 + 1.5.4 ezyhttp-server-graphql ezyhttp-server-graphql diff --git a/ezyhttp-server-graphql/src/main/java/com/tvd12/ezyhttp/server/graphql/data/GraphQLDataFilter.java b/ezyhttp-server-graphql/src/main/java/com/tvd12/ezyhttp/server/graphql/data/GraphQLDataFilter.java index f0a1230..1caa623 100644 --- a/ezyhttp-server-graphql/src/main/java/com/tvd12/ezyhttp/server/graphql/data/GraphQLDataFilter.java +++ b/ezyhttp-server-graphql/src/main/java/com/tvd12/ezyhttp/server/graphql/data/GraphQLDataFilter.java @@ -22,23 +22,42 @@ public Map filter( GraphQLField queryDefinition ) { Map answer = new HashMap<>(); - Map parentMap = null; Deque stack = new ArrayDeque<>(); - stack.push(new StackEntry(queryDefinition, data)); + stack.push(new StackEntry(queryDefinition, data, answer)); + filterStack(stack); + return answer; + } + + @SuppressWarnings({"rawtypes"}) + public List filterList( + List dataList, + GraphQLField queryDefinition + ) { + List answer = new LinkedList<>(); + Deque stack = new ArrayDeque<>(); + pushListItems( + stack, + dataList, + queryDefinition, + answer, + queryDefinition.getName() + ); + filterStack(stack); + return answer; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private void filterStack(Deque stack) { while (!stack.isEmpty()) { StackEntry entry = stack.pop(); - String parentName = entry.field.getName(); - parentMap = parentMap == null - ? answer - : (Map) parentMap.get(parentName); GraphQLField allField = entry.field.getField(ALL_FIELDS); - if (allField != null) { + if (allField != null && entry.data != null) { Set entries = entry.data.entrySet(); for (Map.Entry e : entries) { Object v = e.getValue(); if (v != null) { - parentMap.put(e.getKey(), v); + entry.output.put(e.getKey(), v); } } } @@ -53,41 +72,59 @@ public Map filter( continue; } if (field.getFields().isEmpty()) { - parentMap.put(fieldName, value); + entry.output.put(fieldName, value); continue; } if (value instanceof Map) { - Object newItem = new HashMap<>(); - parentMap.put(fieldName, newItem); - stack.push(new StackEntry(field, (Map) value)); + Map newItem = new HashMap<>(); + entry.output.put(fieldName, newItem); + stack.push(new StackEntry(field, (Map) value, newItem)); } else if (value instanceof List) { - parentMap.put( - fieldName, - filterList((List) value, field) + List newList = new LinkedList<>(); + entry.output.put(fieldName, newList); + pushListItems( + stack, + (List) value, + field, + newList, + fieldName ); } else { - throw new GraphQLInvalidSchemeException( - EzyMapBuilder.mapBuilder() - .put("schema", "invalid") - .put("field", fieldName) - .toMap() - ); + throw newInvalidSchemeException(fieldName); } } } - return answer; } - @SuppressWarnings({"rawtypes"}) - public List filterList( - List dataList, - GraphQLField queryDefinition + @SuppressWarnings({"rawtypes", "unchecked"}) + private void pushListItems( + Deque stack, + List dataList, + GraphQLField queryDefinition, + List answer, + String fieldName ) { - List answer = new LinkedList<>(); - for (Map map : dataList) { - answer.add(filter(map, queryDefinition)); + int size = dataList.size(); + for (int i = size - 1; i >= 0; --i) { + Object data = dataList.get(i); + if (!(data instanceof Map)) { + throw newInvalidSchemeException(fieldName); + } + Map item = new HashMap<>(); + answer.add(0, item); + stack.push(new StackEntry(queryDefinition, (Map) data, item)); } - return answer; + } + + private GraphQLInvalidSchemeException newInvalidSchemeException( + String fieldName + ) { + return new GraphQLInvalidSchemeException( + EzyMapBuilder.mapBuilder() + .put("schema", "invalid") + .put("field", fieldName) + .toMap() + ); } @AllArgsConstructor @@ -95,5 +132,6 @@ public List filterList( private static class StackEntry { private GraphQLField field; private Map data; + private Map output; } } diff --git a/ezyhttp-server-graphql/src/main/java/com/tvd12/ezyhttp/server/graphql/data/GraphQLField.java b/ezyhttp-server-graphql/src/main/java/com/tvd12/ezyhttp/server/graphql/data/GraphQLField.java index 6ff8123..42173a0 100644 --- a/ezyhttp-server-graphql/src/main/java/com/tvd12/ezyhttp/server/graphql/data/GraphQLField.java +++ b/ezyhttp-server-graphql/src/main/java/com/tvd12/ezyhttp/server/graphql/data/GraphQLField.java @@ -5,8 +5,10 @@ import com.tvd12.ezyhttp.core.codec.SingletonStringDeserializer; import lombok.Getter; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; +import java.util.Deque; import java.util.List; import java.util.Map; @@ -103,7 +105,7 @@ public T getFieldArgumentValue( ) { GraphQLField field = this; for (String fieldName : fieldNames) { - field = fieldByName.get(fieldName); + field = field.getField(fieldName); if (field == null) { break; } @@ -147,6 +149,11 @@ public T getFieldArgumentValue( if (value == null) { return null; } + if (value instanceof String) { + return SingletonStringDeserializer + .getInstance() + .deserializeOrNull((String) value, type); + } return (T) EzySingletonOutputTransformer .getInstance() .transform(value, type); @@ -162,15 +169,48 @@ private static String toString( Map arguments, List fields ) { - StringBuilder builder = new StringBuilder() - .append(name); + StringBuilder builder = new StringBuilder(); + Deque stack = new ArrayDeque<>(); + stack.push(new ToStringTask(name, arguments, fields)); + while (!stack.isEmpty()) { + ToStringTask task = stack.pop(); + if (task.text != null) { + builder.append(task.text); + continue; + } + appendFieldStart(builder, task.name, task.arguments); + List taskFields = task.fields; + if (taskFields == null) { + continue; + } + builder.append(", ["); + stack.push(new ToStringTask("]")); + for (int i = taskFields.size() - 1; i >= 0; --i) { + GraphQLField field = taskFields.get(i); + stack.push( + new ToStringTask( + field.name, + field.arguments, + field.fields + ) + ); + if (i > 0) { + stack.push(new ToStringTask(", ")); + } + } + } + return builder.toString(); + } + + private static void appendFieldStart( + StringBuilder builder, + String name, + Map arguments + ) { + builder.append(name); if (arguments != null && !arguments.isEmpty()) { builder.append("(").append(arguments).append(")"); } - if (fields != null) { - builder.append(", ").append(fields); - } - return builder.toString(); } @Override @@ -226,4 +266,29 @@ public String toString() { return GraphQLField.toString(name, arguments, fields); } } + + private static class ToStringTask { + private final String text; + private final String name; + private final Map arguments; + private final List fields; + + ToStringTask(String text) { + this.text = text; + this.name = null; + this.arguments = null; + this.fields = null; + } + + ToStringTask( + String name, + Map arguments, + List fields + ) { + this.text = null; + this.name = name; + this.arguments = arguments; + this.fields = fields; + } + } } diff --git a/ezyhttp-server-graphql/src/main/java/com/tvd12/ezyhttp/server/graphql/scheme/GraphQLSchemaParser.java b/ezyhttp-server-graphql/src/main/java/com/tvd12/ezyhttp/server/graphql/scheme/GraphQLSchemaParser.java index 9932437..008c337 100644 --- a/ezyhttp-server-graphql/src/main/java/com/tvd12/ezyhttp/server/graphql/scheme/GraphQLSchemaParser.java +++ b/ezyhttp-server-graphql/src/main/java/com/tvd12/ezyhttp/server/graphql/scheme/GraphQLSchemaParser.java @@ -9,6 +9,7 @@ import java.util.ArrayDeque; import java.util.Deque; +import java.util.List; import java.util.Map; import static com.tvd12.ezyfox.io.EzyStrings.EMPTY_STRING; @@ -18,6 +19,9 @@ public final class GraphQLSchemaParser { private final ObjectMapper objectMapper; + private static final String VARIABLE_PLACEHOLDER_FIELD = + "__ezyhttp_graphql_variable__"; + @SuppressWarnings({"unchecked", "MethodLength"}) public GraphQLSchema parseQuery( String queryToParse, @@ -66,12 +70,12 @@ public GraphQLSchema parseQuery( ); String arguments = "{" + argumentsBuilder + "}"; try { - childBuilder.arguments( - objectMapper.readValue( - arguments, - Map.class - ) + Map argumentMap = objectMapper.readValue( + arguments, + Map.class ); + replaceVariablePlaceholders(argumentMap, variables); + childBuilder.arguments(argumentMap); } catch (Exception e) { throw new GraphQLObjectMapperException( EzyMapBuilder.mapBuilder() @@ -209,6 +213,9 @@ private StringBuilder forwardStandardize(String query) { if (ch == '{' || ch == '}') { answer.append(ch); } else if (ch == '+' || ch == ',' || ch == ' ' || ch == '\t' || ch == '\n') { + if (answer.length() == 0) { + continue; + } char lastChar = answer.charAt(answer.length() - 1); if ((lastChar != ' ') && (lastChar != '{')) { answer.append(' '); @@ -228,6 +235,9 @@ private StringBuilder backwardStandardize(String query) { if (ch == '{' || ch == '}') { answer.insert(0, ch); } else if (ch == ' ') { // ',' '\t' '\n' '+' have been removed in forward pass + if (answer.length() == 0) { + continue; + } char firstChar = answer.charAt(0); if ((firstChar != '{') && (firstChar != '}')) { answer.insert(0, ' '); @@ -241,12 +251,46 @@ private StringBuilder backwardStandardize(String query) { private String removeQueryPrefix(String s) { String prefix = "query"; - if (s.startsWith(prefix)) { - return s.substring(prefix.length()); + if (s.startsWith(prefix) + && (s.length() == prefix.length() + || !isGraphQLNameChar(s.charAt(prefix.length()))) + ) { + int selectionStart = findOperationSelectionStart( + s, + prefix.length() + ); + return selectionStart >= 0 + ? s.substring(selectionStart) + : s.substring(prefix.length()); } return s; } + private int findOperationSelectionStart(String s, int start) { + int parenthesesCount = 0; + int quoteCount = 0; + int quotesCount = 0; + int length = s.length(); + for (int i = start; i < length; ++i) { + char prevCh = i > 0 ? s.charAt(i - 1) : 0; + char ch = s.charAt(i); + if (prevCh != '\\' && ch == '\'') { + quoteCount = quoteCount == 0 ? 1 : 0; + } else if (prevCh != '\\' && ch == '"') { + quotesCount = quotesCount == 0 ? 1 : 0; + } else if (quoteCount == 0 && quotesCount == 0) { + if (ch == '(') { + ++parenthesesCount; + } else if (ch == ')') { + --parenthesesCount; + } else if (ch == '{' && parenthesesCount == 0) { + return i; + } + } + } + return -1; + } + private int extractQueryArguments( StringBuilder argumentsBuilder, String query, @@ -270,22 +314,19 @@ private int extractQueryArguments( StringBuilder varNameBuilder = new StringBuilder(); for (++i; i < queryLength; ++i) { ch = query.charAt(i); - if (ch == ' ') { - continue; - } - if (ch != ',' && ch != ')' && ch != '}') { + if (isGraphQLNameChar(ch)) { varNameBuilder.append(ch); } else { --i; break; } } - String varName = varNameBuilder.toString(); - Object value = variables.get(varName); - if (value instanceof String) { - value = "\"" + value + "\""; - } - argumentsBuilder.append(value); + argumentsBuilder + .append("{\"") + .append(VARIABLE_PLACEHOLDER_FIELD) + .append("\":\"") + .append(varNameBuilder) + .append("\"}"); continue; } argumentsBuilder.append(ch); @@ -293,6 +334,68 @@ private int extractQueryArguments( return i; } + @SuppressWarnings("unchecked") + private void replaceVariablePlaceholders( + Map arguments, + Map variables + ) { + if (arguments == null || variables == null) { + return; + } + Deque stack = new ArrayDeque<>(); + stack.push(arguments); + while (!stack.isEmpty()) { + Object item = stack.pop(); + if (item instanceof Map) { + Map map = (Map) item; + for (Map.Entry entry : map.entrySet()) { + Object value = entry.getValue(); + if (isVariablePlaceholder(value)) { + entry.setValue(getVariableValue(value, variables)); + } else if (value instanceof Map || value instanceof List) { + stack.push(value); + } + } + } else { + List list = (List) item; + int size = list.size(); + for (int i = 0; i < size; ++i) { + Object value = list.get(i); + if (isVariablePlaceholder(value)) { + list.set(i, getVariableValue(value, variables)); + } else if (value instanceof Map || value instanceof List) { + stack.push(value); + } + } + } + } + } + + private boolean isVariablePlaceholder(Object value) { + return value instanceof Map + && ((Map) value).size() == 1 + && ((Map) value).containsKey(VARIABLE_PLACEHOLDER_FIELD); + } + + private Object getVariableValue( + Object placeholder, + Map variables + ) { + Object variableName = ((Map) placeholder).get( + VARIABLE_PLACEHOLDER_FIELD + ); + return variableName instanceof String + ? variables.get(variableName) + : null; + } + + private boolean isGraphQLNameChar(char ch) { + return (ch >= 'A' && ch <= 'Z') + || (ch >= 'a' && ch <= 'z') + || (ch >= '0' && ch <= '9') + || ch == '_'; + } + private GraphQLField.Builder peekFieldStackItemOrThrow( Deque stack, String message diff --git a/ezyhttp-server-graphql/src/test/java/com/tvd12/ezyhttp/server/graphql/test/data/GraphQLDataFilterTest.java b/ezyhttp-server-graphql/src/test/java/com/tvd12/ezyhttp/server/graphql/test/data/GraphQLDataFilterTest.java index 3dce23c..131fad2 100644 --- a/ezyhttp-server-graphql/src/test/java/com/tvd12/ezyhttp/server/graphql/test/data/GraphQLDataFilterTest.java +++ b/ezyhttp-server-graphql/src/test/java/com/tvd12/ezyhttp/server/graphql/test/data/GraphQLDataFilterTest.java @@ -2,12 +2,18 @@ import com.tvd12.ezyhttp.server.graphql.data.GraphQLDataFilter; import com.tvd12.ezyhttp.server.graphql.data.GraphQLField; +import com.tvd12.ezyhttp.server.graphql.exception.GraphQLInvalidSchemeException; import com.tvd12.test.assertion.Asserts; import org.testng.annotations.Test; +import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.Map; +import static com.tvd12.ezyfox.util.EzyMapBuilder.mapBuilder; +import static com.tvd12.ezyhttp.server.graphql.constants.GraphQLConstants.ALL_FIELDS; + public class GraphQLDataFilterTest { private final GraphQLDataFilter instance = new GraphQLDataFilter(); @@ -29,4 +35,201 @@ public void filterWithNullDataTest() { // then Asserts.assertEquals(result, Collections.emptyMap(), false); } + + @Test + public void filterWithAllFieldsTest() { + // given + GraphQLField queryDefinition = GraphQLField.builder() + .name("user") + .addField( + GraphQLField.builder() + .name(ALL_FIELDS) + .build() + ) + .build(); + Map data = mapBuilder() + .put("id", 1) + .put("name", "Dzung") + .put("nullable", null) + .toMap(); + + // when + Map result = instance.filter(data, queryDefinition); + + // then + Asserts.assertEquals( + result, + mapBuilder() + .put("id", 1) + .put("name", "Dzung") + .toMap(), + false + ); + } + + @Test + public void filterWithAllFieldsAndNullDataTest() { + // given + GraphQLField queryDefinition = GraphQLField.builder() + .name("user") + .addField( + GraphQLField.builder() + .name(ALL_FIELDS) + .build() + ) + .build(); + + // when + Map result = instance.filter(null, queryDefinition); + + // then + Asserts.assertEquals(result, Collections.emptyMap(), false); + } + + @Test + public void filterWithAllFieldsAndNestedOverrideTest() { + // given + GraphQLField queryDefinition = GraphQLField.builder() + .name("user") + .addField( + GraphQLField.builder() + .name(ALL_FIELDS) + .build() + ) + .addField( + GraphQLField.builder() + .name("friends") + .addField( + GraphQLField.builder() + .name("name") + .build() + ) + .build() + ) + .build(); + Map data = mapBuilder() + .put("id", 1) + .put( + "friends", + Arrays.asList( + mapBuilder() + .put("id", 2) + .put("name", "Foo") + .toMap(), + mapBuilder() + .put("id", 3) + .put("name", "Bar") + .toMap() + ) + ) + .toMap(); + + // when + Map result = instance.filter(data, queryDefinition); + + // then + Asserts.assertEquals( + result, + mapBuilder() + .put("id", 1) + .put( + "friends", + Arrays.asList( + mapBuilder() + .put("name", "Foo") + .toMap(), + mapBuilder() + .put("name", "Bar") + .toMap() + ) + ) + .toMap(), + false + ); + } + + @Test + public void filterListWithAllFieldsTest() { + // given + GraphQLField queryDefinition = GraphQLField.builder() + .name("friends") + .addField( + GraphQLField.builder() + .name(ALL_FIELDS) + .build() + ) + .build(); + + // when + Object result = instance.filterList( + Arrays.asList( + new HashMap<>( + mapBuilder() + .put("id", 1) + .put("name", "Foo") + .toMap() + ), + new HashMap<>( + mapBuilder() + .put("id", 2) + .put("name", "Bar") + .toMap() + ) + ), + queryDefinition + ); + + // then + Asserts.assertEquals( + result, + Arrays.asList( + mapBuilder() + .put("id", 1) + .put("name", "Foo") + .toMap(), + mapBuilder() + .put("id", 2) + .put("name", "Bar") + .toMap() + ), + false + ); + } + + @Test + public void filterListFieldWithNonMapItemTest() { + // given + GraphQLField queryDefinition = GraphQLField.builder() + .name("user") + .addField( + GraphQLField.builder() + .name("tags") + .addField( + GraphQLField.builder() + .name("name") + .build() + ) + .build() + ) + .build(); + Map data = mapBuilder() + .put("tags", Arrays.asList("java", "graphql")) + .toMap(); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.filter(data, queryDefinition) + ); + + // then + Asserts.assertEqualsType(e, GraphQLInvalidSchemeException.class); + Asserts.assertEquals( + ((GraphQLInvalidSchemeException) e).getErrors(), + mapBuilder() + .put("schema", "invalid") + .put("field", "tags") + .toMap(), + false + ); + } } diff --git a/ezyhttp-server-graphql/src/test/java/com/tvd12/ezyhttp/server/graphql/test/data/GraphQLFieldTest.java b/ezyhttp-server-graphql/src/test/java/com/tvd12/ezyhttp/server/graphql/test/data/GraphQLFieldTest.java index 274545c..321c091 100644 --- a/ezyhttp-server-graphql/src/test/java/com/tvd12/ezyhttp/server/graphql/test/data/GraphQLFieldTest.java +++ b/ezyhttp-server-graphql/src/test/java/com/tvd12/ezyhttp/server/graphql/test/data/GraphQLFieldTest.java @@ -83,4 +83,102 @@ public void toStringTest() { .arguments(Collections.emptyMap()) ); } + + @Test + public void toStringWithMultipleFieldsTest() { + // given + GraphQLField field = GraphQLField.builder() + .name("user") + .addField( + GraphQLField.builder() + .name("name") + .build() + ) + .addField( + GraphQLField.builder() + .name("email") + .build() + ) + .build(); + + // when + String result = field.toString(); + + // then + Asserts.assertEquals(result, "user, [name, [], email, []]"); + } + + @Test + public void getNestedFieldArgumentValueTest() { + // given + GraphQLField field = GraphQLField.builder() + .name("user") + .addField( + GraphQLField.builder() + .name("bank") + .addField( + GraphQLField.builder() + .name("branch") + .arguments( + EzyMapBuilder.mapBuilder() + .put("id", "1") + .toMap() + ) + .build() + ) + .build() + ) + .build(); + + // when + String value = field.getFieldArgumentValue( + "id", + "bank", + "branch" + ); + Long typedValue = field.getFieldArgumentValue( + "id", + Long.class, + "bank", + "branch" + ); + + // then + Asserts.assertEquals(value, "1"); + Asserts.assertEquals(typedValue, 1L); + } + + @Test + public void getNestedFieldArgumentValueWithNonStringValueTest() { + // given + GraphQLField field = GraphQLField.builder() + .name("user") + .addField( + GraphQLField.builder() + .name("bank") + .addField( + GraphQLField.builder() + .name("branch") + .arguments( + EzyMapBuilder.mapBuilder() + .put("id", 1) + .toMap() + ) + .build() + ) + .build() + ) + .build(); + + // when + Long typedValue = field.getFieldArgumentValue( + "id", + Long.class, + "bank", + "branch" + ); + + // then + Asserts.assertEquals(typedValue, 1L); + } } diff --git a/ezyhttp-server-graphql/src/test/java/com/tvd12/ezyhttp/server/graphql/test/scheme/GraphQLSchemaParserTest.java b/ezyhttp-server-graphql/src/test/java/com/tvd12/ezyhttp/server/graphql/test/scheme/GraphQLSchemaParserTest.java index 0f9ac56..9dc59f3 100644 --- a/ezyhttp-server-graphql/src/test/java/com/tvd12/ezyhttp/server/graphql/test/scheme/GraphQLSchemaParserTest.java +++ b/ezyhttp-server-graphql/src/test/java/com/tvd12/ezyhttp/server/graphql/test/scheme/GraphQLSchemaParserTest.java @@ -91,6 +91,42 @@ public void testStandardize3() { } } + @Test + public void standardizeQueryWithLeadingSeparatorTest() { + // given + GraphQLSchemaParser parser = new GraphQLSchemaParser( + new ObjectMapper() + ); + + // when + GraphQLSchema schema = parser.parseQuery( + ",{slug}", + Collections.emptyMap() + ); + + // then + Asserts.assertEquals(schema.getQueryDefinitions().size(), 1); + Asserts.assertEquals(schema.getQueryDefinitions().get(0).getName(), "slug"); + } + + @Test + public void standardizeQueryWithTrailingSeparatorTest() { + // given + GraphQLSchemaParser parser = new GraphQLSchemaParser( + new ObjectMapper() + ); + + // when + GraphQLSchema schema = parser.parseQuery( + "{slug},", + Collections.emptyMap() + ); + + // then + Asserts.assertEquals(schema.getQueryDefinitions().size(), 1); + Asserts.assertEquals(schema.getQueryDefinitions().get(0).getName(), "slug"); + } + @Test public void testStandardize4() { // given @@ -368,218 +404,1105 @@ public void parseQueryNormalWithArgsSpecialCase() { } @Test - public void parseQueryInvalidArgumentTest() { + public void parseQueryWithStringVariableSpecialChars() { // given GraphQLSchemaParser instance = new GraphQLSchemaParser( - new ObjectMapper() + new GraphQLObjectMapperFactory().newObjectMapper() ); // when - Throwable e = Asserts.assertThrows(() -> - instance.parseQuery( - "{me(hello: world){}}", - Collections.emptyMap() - ) + GraphQLSchema schema = instance.parseQuery( + "{me(name: $name){}}", + Collections.singletonMap("name", "he\"l\\lo\nworld") ); // then - Asserts.assertEqualsType(e, GraphQLObjectMapperException.class); + Asserts.assertEquals( + schema.getQueryDefinitions().get(0).getArguments(), + EzyMapBuilder.mapBuilder() + .put("name", "he\"l\\lo\nworld") + .toMap(), + false + ); } @Test - public void parseQueryInvalidArgument2Test() { + public void parseQueryWithVariablesInListAndNestedObject() { // given GraphQLSchemaParser instance = new GraphQLSchemaParser( - new ObjectMapper() + new GraphQLObjectMapperFactory().newObjectMapper() ); // when - Throwable e = Asserts.assertThrows(() -> - instance.parseQuery( - "{me(hello: world}", - Collections.emptyMap() - ) + GraphQLSchema schema = instance.parseQuery( + "{me(ids: [$id], filter: {name: $name}){}}", + EzyMapBuilder.mapBuilder() + .put("id", 1) + .put("name", "Dzung") + .toMap() ); // then - Asserts.assertEqualsType(e, GraphQLObjectMapperException.class); + Asserts.assertEquals( + schema.getQueryDefinitions().get(0).getArguments(), + EzyMapBuilder.mapBuilder() + .put("ids", Collections.singletonList(1)) + .put( + "filter", + EzyMapBuilder.mapBuilder() + .put("name", "Dzung") + .toMap() + ) + .toMap(), + false + ); } @Test - public void parseQueryInvalidArgument3Test() { + public void replaceVariablePlaceholdersWithNullArgumentsTest() throws Exception { // given GraphQLSchemaParser instance = new GraphQLSchemaParser( - new ObjectMapper() + new GraphQLObjectMapperFactory().newObjectMapper() ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "replaceVariablePlaceholders", + Map.class, + Map.class + ); + method.setAccessible(true); // when - Throwable e = Asserts.assertThrows(() -> - instance.parseQuery( - "{me(hello: $world", - Collections.singletonMap("hello", "world") - ) + method.invoke( + instance, + null, + Collections.singletonMap("id", 1) ); // then - Asserts.assertEqualsType(e, GraphQLObjectMapperException.class); + Asserts.assertTrue(true); } @Test - public void parseQueryInvalidArgument3xTest() { + public void replaceVariablePlaceholdersWithNullVariablesTest() throws Exception { // given GraphQLSchemaParser instance = new GraphQLSchemaParser( - new ObjectMapper() + new GraphQLObjectMapperFactory().newObjectMapper() ); - - StringBuilder argumentsBuilder = new StringBuilder(); - String query = "{me(hello: $"; - int queryLength = query.length(); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "replaceVariablePlaceholders", + Map.class, + Map.class + ); + method.setAccessible(true); + Map arguments = EzyMapBuilder.mapBuilder() + .put("id", 1) + .toMap(); // when - Integer i = MethodInvoker.create() - .object(instance) - .method("extractQueryArguments") - .param(StringBuilder.class, argumentsBuilder) - .param(String.class, query) - .param(int.class, 0) - .param(int.class, queryLength) - .param(Map.class, Collections.singletonMap("hello", "world")) - .invoke(Integer.class); + method.invoke( + instance, + arguments, + null + ); // then - Asserts.assertEquals(i, queryLength + 1); + Asserts.assertEquals( + arguments, + EzyMapBuilder.mapBuilder() + .put("id", 1) + .toMap(), + false + ); } @Test - public void parseQueryInvalidArgument4Test() { + public void replaceVariablePlaceholdersWithListValueTest() throws Exception { // given GraphQLSchemaParser instance = new GraphQLSchemaParser( - new ObjectMapper() + new GraphQLObjectMapperFactory().newObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "replaceVariablePlaceholders", + Map.class, + Map.class ); + method.setAccessible(true); + Map arguments = EzyMapBuilder.mapBuilder() + .put( + "ids", + Arrays.asList( + EzyMapBuilder.mapBuilder() + .put("__ezyhttp_graphql_variable__", "id") + .toMap() + ) + ) + .toMap(); // when - Throwable e = Asserts.assertThrows(() -> - instance.parseQuery( - "{me(hello: '\"world)", - Collections.emptyMap() - ) + method.invoke( + instance, + arguments, + Collections.singletonMap("id", 1) ); // then - Asserts.assertEqualsType(e, GraphQLObjectMapperException.class); + Asserts.assertEquals( + arguments, + EzyMapBuilder.mapBuilder() + .put("ids", Collections.singletonList(1)) + .toMap(), + false + ); } @Test - public void parseQueryInvalidArgument5Test() { + public void replaceVariablePlaceholdersWithNestedListValueTest() throws Exception { // given GraphQLSchemaParser instance = new GraphQLSchemaParser( - new ObjectMapper() + new GraphQLObjectMapperFactory().newObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "replaceVariablePlaceholders", + Map.class, + Map.class ); + method.setAccessible(true); + Map arguments = EzyMapBuilder.mapBuilder() + .put( + "filters", + Arrays.asList( + EzyMapBuilder.mapBuilder() + .put( + "names", + Arrays.asList( + EzyMapBuilder.mapBuilder() + .put("__ezyhttp_graphql_variable__", "name") + .toMap() + ) + ) + .toMap() + ) + ) + .toMap(); // when - Throwable e = Asserts.assertThrows(() -> - instance.parseQuery( - "{me(hello: 'world)", - Collections.emptyMap() - ) + method.invoke( + instance, + arguments, + Collections.singletonMap("name", "Dzung") ); // then - Asserts.assertEqualsType(e, GraphQLObjectMapperException.class); + Asserts.assertEquals( + arguments, + EzyMapBuilder.mapBuilder() + .put( + "filters", + Collections.singletonList( + EzyMapBuilder.mapBuilder() + .put( + "names", + Collections.singletonList("Dzung") + ) + .toMap() + ) + ) + .toMap(), + false + ); } @Test - public void parseQueryInvalidArgument6Test() { + public void replaceVariablePlaceholdersShouldPushMapItemInListTest() throws Exception { // given GraphQLSchemaParser instance = new GraphQLSchemaParser( - new ObjectMapper() + new GraphQLObjectMapperFactory().newObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "replaceVariablePlaceholders", + Map.class, + Map.class ); + method.setAccessible(true); + Map arguments = EzyMapBuilder.mapBuilder() + .put( + "filters", + Arrays.asList( + EzyMapBuilder.mapBuilder() + .put( + "name", + EzyMapBuilder.mapBuilder() + .put("__ezyhttp_graphql_variable__", "name") + .toMap() + ) + .toMap() + ) + ) + .toMap(); // when - Throwable e = Asserts.assertThrows(() -> - instance.parseQuery( - "{me(hello: \"world)", - Collections.emptyMap() - ) + method.invoke( + instance, + arguments, + Collections.singletonMap("name", "Dzung") ); // then - Asserts.assertEqualsType(e, GraphQLObjectMapperException.class); + Asserts.assertEquals( + arguments, + EzyMapBuilder.mapBuilder() + .put( + "filters", + Collections.singletonList( + EzyMapBuilder.mapBuilder() + .put("name", "Dzung") + .toMap() + ) + ) + .toMap(), + false + ); } @Test - public void parseQueryNoChildBeforeArgumentsTest() { + public void replaceVariablePlaceholdersShouldPushListItemInListTest() throws Exception { // given GraphQLSchemaParser instance = new GraphQLSchemaParser( - new ObjectMapper() + new GraphQLObjectMapperFactory().newObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "replaceVariablePlaceholders", + Map.class, + Map.class ); + method.setAccessible(true); + Map arguments = EzyMapBuilder.mapBuilder() + .put( + "groups", + Arrays.asList( + Arrays.asList( + EzyMapBuilder.mapBuilder() + .put("__ezyhttp_graphql_variable__", "id") + .toMap() + ) + ) + ) + .toMap(); // when - Throwable e = Asserts.assertThrows(() -> - instance.parseQuery( - "(id: 1)", - Collections.emptyMap() - ) + method.invoke( + instance, + arguments, + Collections.singletonMap("id", 1) ); // then - Asserts.assertEqualsType(e, GraphQLObjectMapperException.class); - GraphQLObjectMapperException exception = (GraphQLObjectMapperException) e; Asserts.assertEquals( - exception.getErrors(), + arguments, EzyMapBuilder.mapBuilder() - .put("arguments", "invalid") - .put("message", "there is no child") + .put( + "groups", + Collections.singletonList( + Collections.singletonList(1) + ) + ) .toMap(), false ); } @Test - public void requireParentBuilderWithEmptyStackTest() { + public void replaceVariablePlaceholdersWithScalarListValueTest() throws Exception { // given GraphQLSchemaParser instance = new GraphQLSchemaParser( - new ObjectMapper() + new GraphQLObjectMapperFactory().newObjectMapper() ); - Deque stack = new ArrayDeque<>(); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "replaceVariablePlaceholders", + Map.class, + Map.class + ); + method.setAccessible(true); + Map arguments = EzyMapBuilder.mapBuilder() + .put( + "tags", + Arrays.asList("java", 1, null) + ) + .toMap(); // when - final Method method; - try { - method = GraphQLSchemaParser.class.getDeclaredMethod( - "peekFieldStackItemOrThrow", - Deque.class, - String.class - ); - } catch (NoSuchMethodException ex) { - throw new IllegalStateException( - "method requireParentBuilder should exist", - ex - ); - } - method.setAccessible(true); - Throwable e = Asserts.assertThrows(() -> { - try { - method.invoke( - instance, - stack, - "there is no parent case curly brace close" - ); - } catch (InvocationTargetException ex) { - throw ex.getCause(); - } - }); + method.invoke( + instance, + arguments, + Collections.singletonMap("id", 1) + ); // then - Asserts.assertEqualsType(e, GraphQLObjectMapperException.class); - GraphQLObjectMapperException exception = (GraphQLObjectMapperException) e; Asserts.assertEquals( - exception.getErrors(), + arguments, EzyMapBuilder.mapBuilder() - .put("arguments", "invalid") - .put("message", "there is no parent case curly brace close") + .put( + "tags", + Arrays.asList("java", 1, null) + ) .toMap(), false ); } + + @Test + public void getVariableValueWithStringVariableNameTest() throws Exception { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new GraphQLObjectMapperFactory().newObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "getVariableValue", + Object.class, + Map.class + ); + method.setAccessible(true); + Map placeholder = EzyMapBuilder.mapBuilder() + .put("__ezyhttp_graphql_variable__", "id") + .toMap(); + + // when + Object result = method.invoke( + instance, + placeholder, + Collections.singletonMap("id", 1) + ); + + // then + Asserts.assertEquals(result, 1); + } + + @Test + public void getVariableValueWithNonStringVariableNameTest() throws Exception { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new GraphQLObjectMapperFactory().newObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "getVariableValue", + Object.class, + Map.class + ); + method.setAccessible(true); + Map placeholder = EzyMapBuilder.mapBuilder() + .put("__ezyhttp_graphql_variable__", 1) + .toMap(); + + // when + Object result = method.invoke( + instance, + placeholder, + Collections.singletonMap("id", 1) + ); + + // then + Asserts.assertNull(result); + } + + @Test + public void parseQuerySubmitWorkReportWithOperationVariables() { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new GraphQLObjectMapperFactory().newObjectMapper() + ); + String contentJson = "{\"items\":[{\"title\":\"Done\"}]}"; + String scheduleJson = "[{\"time\":\"09:00\",\"task\":\"Daily\"}]"; + String query = + "query($reportType: String, $reportDate: String, " + + "$employeeName: String, $username: String, " + + "$email: String, $department: String, " + + "$contentJson: String, $scheduleJson: String) {\n" + + " submit_work_report(" + + "reportType: $reportType, " + + "reportDate: $reportDate, " + + "employeeName: $employeeName, " + + "username: $username, " + + "email: $email, " + + "department: $department, " + + "contentJson: $contentJson, " + + "scheduleJson: $scheduleJson" + + ") {\n" + + " success postId slug message\n" + + " }\n" + + "}"; + + // when + GraphQLSchema schema = instance.parseQuery( + query, + EzyMapBuilder.mapBuilder() + .put("reportType", "daily") + .put("reportDate", "2026-06-26") + .put("employeeName", "Dzung") + .put("username", "dzung@example.com") + .put("email", "dzung@example.com") + .put("department", "") + .put("contentJson", contentJson) + .put("scheduleJson", scheduleJson) + .toMap() + ); + + // then + GraphQLField queryDefinition = schema.getQueryDefinitions().get(0); + Asserts.assertEquals(schema.getQueryDefinitions().size(), 1); + Asserts.assertEquals(queryDefinition.getName(), "submit_work_report"); + Asserts.assertEquals( + queryDefinition.getArguments(), + EzyMapBuilder.mapBuilder() + .put("reportType", "daily") + .put("reportDate", "2026-06-26") + .put("employeeName", "Dzung") + .put("username", "dzung@example.com") + .put("email", "dzung@example.com") + .put("department", "") + .put("contentJson", contentJson) + .put("scheduleJson", scheduleJson) + .toMap(), + false + ); + Asserts.assertEquals(queryDefinition.getFields().size(), 4); + Asserts.assertEquals(queryDefinition.getFields().get(0).getName(), "success"); + Asserts.assertEquals(queryDefinition.getFields().get(1).getName(), "postId"); + Asserts.assertEquals(queryDefinition.getFields().get(2).getName(), "slug"); + Asserts.assertEquals(queryDefinition.getFields().get(3).getName(), "message"); + } + + @Test + public void parseQueryInvalidArgumentTest() { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.parseQuery( + "{me(hello: world){}}", + Collections.emptyMap() + ) + ); + + // then + Asserts.assertEqualsType(e, GraphQLObjectMapperException.class); + } + + @Test + public void parseQueryInvalidArgument2Test() { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.parseQuery( + "{me(hello: world}", + Collections.emptyMap() + ) + ); + + // then + Asserts.assertEqualsType(e, GraphQLObjectMapperException.class); + } + + @Test + public void parseQueryInvalidArgument3Test() { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.parseQuery( + "{me(hello: $world", + Collections.singletonMap("hello", "world") + ) + ); + + // then + Asserts.assertEqualsType(e, GraphQLObjectMapperException.class); + } + + @Test + public void parseQueryInvalidArgument3xTest() { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + + StringBuilder argumentsBuilder = new StringBuilder(); + String query = "{me(hello: $"; + int queryLength = query.length(); + + // when + Integer i = MethodInvoker.create() + .object(instance) + .method("extractQueryArguments") + .param(StringBuilder.class, argumentsBuilder) + .param(String.class, query) + .param(int.class, 0) + .param(int.class, queryLength) + .param(Map.class, Collections.singletonMap("hello", "world")) + .invoke(Integer.class); + + // then + Asserts.assertEquals(i, queryLength + 1); + } + + @Test + public void parseQueryInvalidArgument4Test() { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.parseQuery( + "{me(hello: '\"world)", + Collections.emptyMap() + ) + ); + + // then + Asserts.assertEqualsType(e, GraphQLObjectMapperException.class); + } + + @Test + public void parseQueryInvalidArgument5Test() { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.parseQuery( + "{me(hello: 'world)", + Collections.emptyMap() + ) + ); + + // then + Asserts.assertEqualsType(e, GraphQLObjectMapperException.class); + } + + @Test + public void parseQueryInvalidArgument6Test() { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.parseQuery( + "{me(hello: \"world)", + Collections.emptyMap() + ) + ); + + // then + Asserts.assertEqualsType(e, GraphQLObjectMapperException.class); + } + + @Test + public void parseQueryNoChildBeforeArgumentsTest() { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.parseQuery( + "(id: 1)", + Collections.emptyMap() + ) + ); + + // then + Asserts.assertEqualsType(e, GraphQLObjectMapperException.class); + GraphQLObjectMapperException exception = (GraphQLObjectMapperException) e; + Asserts.assertEquals( + exception.getErrors(), + EzyMapBuilder.mapBuilder() + .put("arguments", "invalid") + .put("message", "there is no child") + .toMap(), + false + ); + } + + @Test + public void requireParentBuilderWithEmptyStackTest() { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + Deque stack = new ArrayDeque<>(); + + // when + final Method method; + try { + method = GraphQLSchemaParser.class.getDeclaredMethod( + "peekFieldStackItemOrThrow", + Deque.class, + String.class + ); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException( + "method requireParentBuilder should exist", + ex + ); + } + method.setAccessible(true); + Throwable e = Asserts.assertThrows(() -> { + try { + method.invoke( + instance, + stack, + "there is no parent case curly brace close" + ); + } catch (InvocationTargetException ex) { + throw ex.getCause(); + } + }); + + // then + Asserts.assertEqualsType(e, GraphQLObjectMapperException.class); + GraphQLObjectMapperException exception = (GraphQLObjectMapperException) e; + Asserts.assertEquals( + exception.getErrors(), + EzyMapBuilder.mapBuilder() + .put("arguments", "invalid") + .put("message", "there is no parent case curly brace close") + .toMap(), + false + ); + } + + @Test + public void findOperationSelectionStartFromZeroTest() throws Exception { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "findOperationSelectionStart", + String.class, + int.class + ); + method.setAccessible(true); + + // when + Object result = method.invoke( + instance, + "{slug}", + 0 + ); + + // then + Asserts.assertEquals(result, 0); + } + + @Test + public void findOperationSelectionStartFromLengthTest() throws Exception { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "findOperationSelectionStart", + String.class, + int.class + ); + method.setAccessible(true); + String query = "query"; + + // when + Object result = method.invoke( + instance, + query, + query.length() + ); + + // then + Asserts.assertEquals(result, -1); + } + + @Test + public void findOperationSelectionStartShouldUseStartIndexTest() throws Exception { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "findOperationSelectionStart", + String.class, + int.class + ); + method.setAccessible(true); + + // when + Object result = method.invoke( + instance, + "{ignored} query {slug}", + 10 + ); + + // then + Asserts.assertEquals(result, 16); + } + + @Test + public void findOperationSelectionStartShouldIgnoreBracesInSingleQuotesTest() throws Exception { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "findOperationSelectionStart", + String.class, + int.class + ); + method.setAccessible(true); + + // when + Object result = method.invoke( + instance, + "query(arg: '{ignored}'){slug}", + 5 + ); + + // then + Asserts.assertEquals(result, 23); + } + + @Test + public void findOperationSelectionStartShouldIgnoreBracesInDoubleQuotesTest() throws Exception { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "findOperationSelectionStart", + String.class, + int.class + ); + method.setAccessible(true); + + // when + Object result = method.invoke( + instance, + "query(arg: \"{ignored}\"){slug}", + 5 + ); + + // then + Asserts.assertEquals(result, 23); + } + + @Test + public void findOperationSelectionStartShouldIgnoreEscapedSingleQuoteTest() throws Exception { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "findOperationSelectionStart", + String.class, + int.class + ); + method.setAccessible(true); + + // when + Object result = method.invoke( + instance, + "query(arg: 'it\\'s {ignored}'){slug}", + 5 + ); + + // then + Asserts.assertEquals(result, 29); + } + + @Test + public void findOperationSelectionStartShouldIgnoreEscapedDoubleQuoteTest() throws Exception { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "findOperationSelectionStart", + String.class, + int.class + ); + method.setAccessible(true); + + // when + Object result = method.invoke( + instance, + "query(arg: \"he\\\"llo {ignored}\"){slug}", + 5 + ); + + // then + Asserts.assertEquals(result, 31); + } + + @Test + public void findOperationSelectionStartShouldReturnMinusOneInOpenSingleQuoteTest() throws Exception { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "findOperationSelectionStart", + String.class, + int.class + ); + method.setAccessible(true); + + // when + Object result = method.invoke( + instance, + "query(arg: '{ignored}){slug}", + 5 + ); + + // then + Asserts.assertEquals(result, -1); + } + + @Test + public void findOperationSelectionStartShouldReturnMinusOneInOpenDoubleQuoteTest() throws Exception { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "findOperationSelectionStart", + String.class, + int.class + ); + method.setAccessible(true); + + // when + Object result = method.invoke( + instance, + "query(arg: \"{ignored}){slug}", + 5 + ); + + // then + Asserts.assertEquals(result, -1); + } + + @Test + public void findOperationSelectionStartShouldIgnoreBracesInParenthesesTest() throws Exception { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "findOperationSelectionStart", + String.class, + int.class + ); + method.setAccessible(true); + + // when + Object result = method.invoke( + instance, + "query(input: {id: 1}){slug}", + 5 + ); + + // then + Asserts.assertEquals(result, 21); + } + + @Test + public void findOperationSelectionStartShouldReturnMinusOneWhenBracesOnlyInParenthesesTest() throws Exception { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "findOperationSelectionStart", + String.class, + int.class + ); + method.setAccessible(true); + + // when + Object result = method.invoke( + instance, + "query(input: {id: 1})", + 5 + ); + + // then + Asserts.assertEquals(result, -1); + } + + @Test + public void removeQueryPrefixWithExactPrefixTest() throws Exception { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "removeQueryPrefix", + String.class + ); + method.setAccessible(true); + + // when + Object result = method.invoke( + instance, + "query" + ); + + // then + Asserts.assertEquals(result, ""); + } + + @Test + public void removeQueryPrefixWithNonNameCharAfterPrefixTest() throws Exception { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "removeQueryPrefix", + String.class + ); + method.setAccessible(true); + + // when + Object result = method.invoke( + instance, + "query{slug}" + ); + + // then + Asserts.assertEquals(result, "{slug}"); + } + + @Test + public void removeQueryPrefixWithOperationNameAndSelectionStartTest() throws Exception { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "removeQueryPrefix", + String.class + ); + method.setAccessible(true); + + // when + Object result = method.invoke( + instance, + "query FindSlug($id: ID){slug}" + ); + + // then + Asserts.assertEquals(result, "{slug}"); + } + + @Test + public void removeQueryPrefixWithoutSelectionStartAfterPrefixTest() throws Exception { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "removeQueryPrefix", + String.class + ); + method.setAccessible(true); + + // when + Object result = method.invoke( + instance, + "query FindSlug" + ); + + // then + Asserts.assertEquals(result, " FindSlug"); + } + + @Test + public void removeQueryPrefixWithLetterAfterPrefixTest() throws Exception { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "removeQueryPrefix", + String.class + ); + method.setAccessible(true); + + // when + Object result = method.invoke( + instance, + "queryName{slug}" + ); + + // then + Asserts.assertEquals(result, "queryName{slug}"); + } + + @Test + public void removeQueryPrefixWithDigitAfterPrefixTest() throws Exception { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "removeQueryPrefix", + String.class + ); + method.setAccessible(true); + + // when + Object result = method.invoke( + instance, + "query1{slug}" + ); + + // then + Asserts.assertEquals(result, "query1{slug}"); + } + + @Test + public void removeQueryPrefixWithUnderscoreAfterPrefixTest() throws Exception { + // given + GraphQLSchemaParser instance = new GraphQLSchemaParser( + new ObjectMapper() + ); + Method method = GraphQLSchemaParser.class.getDeclaredMethod( + "removeQueryPrefix", + String.class + ); + method.setAccessible(true); + + // when + Object result = method.invoke( + instance, + "query_name{slug}" + ); + + // then + Asserts.assertEquals(result, "query_name{slug}"); + } } diff --git a/ezyhttp-server-jetty/pom.xml b/ezyhttp-server-jetty/pom.xml index 98be403..9637ab5 100644 --- a/ezyhttp-server-jetty/pom.xml +++ b/ezyhttp-server-jetty/pom.xml @@ -5,7 +5,7 @@ com.tvd12 ezyhttp - 1.5.3 + 1.5.4 ezyhttp-server-jetty diff --git a/ezyhttp-server-management/pom.xml b/ezyhttp-server-management/pom.xml index 1ea7283..f6c0e4e 100644 --- a/ezyhttp-server-management/pom.xml +++ b/ezyhttp-server-management/pom.xml @@ -6,7 +6,7 @@ com.tvd12 ezyhttp - 1.5.3 + 1.5.4 ezyhttp-server-management ezyhttp-server-management diff --git a/ezyhttp-server-thymeleaf/pom.xml b/ezyhttp-server-thymeleaf/pom.xml index 9c5ea43..21b1251 100644 --- a/ezyhttp-server-thymeleaf/pom.xml +++ b/ezyhttp-server-thymeleaf/pom.xml @@ -6,7 +6,7 @@ com.tvd12 ezyhttp - 1.5.3 + 1.5.4 ezyhttp-server-thymeleaf ezyhttp-server-thymeleaf diff --git a/ezyhttp-server-tomcat/pom.xml b/ezyhttp-server-tomcat/pom.xml index a6199bb..1565073 100644 --- a/ezyhttp-server-tomcat/pom.xml +++ b/ezyhttp-server-tomcat/pom.xml @@ -5,7 +5,7 @@ com.tvd12 ezyhttp - 1.5.3 + 1.5.4 ezyhttp-server-tomcat diff --git a/pom.xml b/pom.xml index 84c9499..3358de1 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ 1.0.7 ezyhttp - 1.5.3 + 1.5.4 pom ezyhttp