From f12099f7dd1f27f65f39a1788f49975fdc5f795e Mon Sep 17 00:00:00 2001 From: nrsureshdeveloper <86210937+nrsureshdeveloper@users.noreply.github.com> Date: Fri, 15 Apr 2022 01:49:56 -0500 Subject: [PATCH] Bael-5306: graphql error handling (#12041) * Example implementation of Hexagonal Architecture pattern * BAEL-5306 - spring boot/graphql error handling example * removed the ddd hexagonal arch module Co-authored-by: Suresh Raghavan --- graphql/graphql-error-handling/pom.xml | 97 +++++++++++++++++++ .../GraphQLErrorHandlerApplication.java | 46 +++++++++ .../error/handling/domain/Location.java | 29 ++++++ .../error/handling/domain/Vehicle.java | 26 +++++ .../exception/AbstractGraphQLException.java | 44 +++++++++ .../exception/GraphQLErrorAdapter.java | 48 +++++++++ .../exception/InvalidInputException.java | 7 ++ .../VehicleAlreadyPresentException.java | 14 +++ .../exception/VehicleNotFoundException.java | 14 +++ .../repository/InventoryRepository.java | 9 ++ .../repository/LocationRepository.java | 9 ++ .../error/handling/resolver/Mutation.java | 20 ++++ .../error/handling/resolver/Query.java | 29 ++++++ .../handling/service/InventoryService.java | 67 +++++++++++++ .../src/main/resources/application.yml | 23 +++++ .../main/resources/graphql/inventory.graphqls | 23 +++++ .../src/main/resources/import.sql | 7 ++ ...rrorHandlerApplicationIntegrationTest.java | 55 +++++++++++ .../graphql/error/handling/TestUtils.java | 15 +++ ...t_non_null_fields_partial_response.graphql | 10 ++ ...quest_error_invalid_request_syntax.graphql | 9 ++ .../request_error_unknown_operation.graphql | 9 ++ ...uest_non_null_fields_partial_response.json | 34 +++++++ .../request_error_invalid_request_syntax.json | 18 ++++ .../request_error_unknown_operation.json | 18 ++++ .../src/test/resources/init_script.sql | 0 26 files changed, 680 insertions(+) create mode 100644 graphql/graphql-error-handling/pom.xml create mode 100644 graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/GraphQLErrorHandlerApplication.java create mode 100644 graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/domain/Location.java create mode 100644 graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/domain/Vehicle.java create mode 100644 graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/exception/AbstractGraphQLException.java create mode 100644 graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/exception/GraphQLErrorAdapter.java create mode 100644 graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/exception/InvalidInputException.java create mode 100644 graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/exception/VehicleAlreadyPresentException.java create mode 100644 graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/exception/VehicleNotFoundException.java create mode 100644 graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/repository/InventoryRepository.java create mode 100644 graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/repository/LocationRepository.java create mode 100644 graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/resolver/Mutation.java create mode 100644 graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/resolver/Query.java create mode 100644 graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/service/InventoryService.java create mode 100644 graphql/graphql-error-handling/src/main/resources/application.yml create mode 100644 graphql/graphql-error-handling/src/main/resources/graphql/inventory.graphqls create mode 100644 graphql/graphql-error-handling/src/main/resources/import.sql create mode 100644 graphql/graphql-error-handling/src/test/java/com/baeldung/graphql/error/handling/GraphQLErrorHandlerApplicationIntegrationTest.java create mode 100644 graphql/graphql-error-handling/src/test/java/com/baeldung/graphql/error/handling/TestUtils.java create mode 100644 graphql/graphql-error-handling/src/test/resources/graphql/request/field_error_request_non_null_fields_partial_response.graphql create mode 100644 graphql/graphql-error-handling/src/test/resources/graphql/request/request_error_invalid_request_syntax.graphql create mode 100644 graphql/graphql-error-handling/src/test/resources/graphql/request/request_error_unknown_operation.graphql create mode 100644 graphql/graphql-error-handling/src/test/resources/graphql/response/field_error_request_non_null_fields_partial_response.json create mode 100644 graphql/graphql-error-handling/src/test/resources/graphql/response/request_error_invalid_request_syntax.json create mode 100644 graphql/graphql-error-handling/src/test/resources/graphql/response/request_error_unknown_operation.json create mode 100644 graphql/graphql-error-handling/src/test/resources/init_script.sql diff --git a/graphql/graphql-error-handling/pom.xml b/graphql/graphql-error-handling/pom.xml new file mode 100644 index 0000000000..0cd00df3b9 --- /dev/null +++ b/graphql/graphql-error-handling/pom.xml @@ -0,0 +1,97 @@ + + + 4.0.0 + com.baeldung.graphql + graphql-error-handling + 1.0 + jar + graphql-error-handling + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../../parent-boot-2 + + + + 1.8 + 1.18.18 + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-web + + + + com.graphql-java + graphql-spring-boot-starter + 5.0.2 + + + + com.graphql-java + graphql-java-tools + 5.2.4 + + + + org.projectlombok + lombok + ${lombok.version} + + + + com.h2database + h2 + ${h2.version} + + + + org.springframework.boot + spring-boot-test + test + 2.6.4 + + + + com.graphql-java + graphql-spring-boot-starter-test + test + 5.0.2 + + + + org.assertj + assertj-core + 3.22.0 + test + + + + org.skyscreamer + jsonassert + 1.5.0 + test + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file diff --git a/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/GraphQLErrorHandlerApplication.java b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/GraphQLErrorHandlerApplication.java new file mode 100644 index 0000000000..565c9e0a15 --- /dev/null +++ b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/GraphQLErrorHandlerApplication.java @@ -0,0 +1,46 @@ +package com.baeldung.graphql.error.handling; + +import com.baeldung.graphql.error.handling.exception.GraphQLErrorAdapter; +import graphql.ExceptionWhileDataFetching; +import graphql.GraphQLError; +import graphql.servlet.GraphQLErrorHandler; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@SpringBootApplication +public class GraphQLErrorHandlerApplication { + public static void main(String[] args) { + SpringApplication.run(GraphQLErrorHandlerApplication.class, args); + } + + @Bean + public GraphQLErrorHandler errorHandler() { + return new GraphQLErrorHandler() { + @Override + public List processErrors(List errors) { + List clientErrors = errors.stream() + .filter(this::isClientError) + .collect(Collectors.toList()); + + List serverErrors = errors.stream() + .filter(e -> !isClientError(e)) + .map(GraphQLErrorAdapter::new) + .collect(Collectors.toList()); + + List e = new ArrayList<>(); + e.addAll(clientErrors); + e.addAll(serverErrors); + return e; + } + + private boolean isClientError(GraphQLError error) { + return !(error instanceof ExceptionWhileDataFetching || error instanceof Throwable); + } + }; + } +} diff --git a/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/domain/Location.java b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/domain/Location.java new file mode 100644 index 0000000000..815bf3a26a --- /dev/null +++ b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/domain/Location.java @@ -0,0 +1,29 @@ +package com.baeldung.graphql.error.handling.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; + +@Data +@Entity +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Location { + @Id + private String zipcode; + + private String city; + private String state; + + @OneToMany(mappedBy = "location", fetch = FetchType.EAGER) + private List vehicles = new ArrayList<>(); +} diff --git a/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/domain/Vehicle.java b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/domain/Vehicle.java new file mode 100644 index 0000000000..e206bdb009 --- /dev/null +++ b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/domain/Vehicle.java @@ -0,0 +1,26 @@ +package com.baeldung.graphql.error.handling.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@Data +@Entity +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Vehicle { + @Id + private String vin; + private Integer year; + private String make; + private String model; + private String trim; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "fk_location") + private Location location; +} diff --git a/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/exception/AbstractGraphQLException.java b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/exception/AbstractGraphQLException.java new file mode 100644 index 0000000000..4e7be50ae4 --- /dev/null +++ b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/exception/AbstractGraphQLException.java @@ -0,0 +1,44 @@ +package com.baeldung.graphql.error.handling.exception; + +import graphql.ErrorType; +import graphql.GraphQLError; +import graphql.language.SourceLocation; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AbstractGraphQLException extends RuntimeException implements GraphQLError { + private Map parameters = new HashMap(); + + public AbstractGraphQLException(String message) { + super(message); + } + + public AbstractGraphQLException(String message, Map additionParams) { + this(message); + if (additionParams != null) { + parameters = additionParams; + } + } + + @Override + public String getMessage() { + return super.getMessage(); + } + + @Override + public List getLocations() { + return null; + } + + @Override + public ErrorType getErrorType() { + return null; + } + + @Override + public Map getExtensions() { + return this.parameters; + } +} diff --git a/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/exception/GraphQLErrorAdapter.java b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/exception/GraphQLErrorAdapter.java new file mode 100644 index 0000000000..d982f98db3 --- /dev/null +++ b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/exception/GraphQLErrorAdapter.java @@ -0,0 +1,48 @@ +package com.baeldung.graphql.error.handling.exception; + +import graphql.ErrorType; +import graphql.ExceptionWhileDataFetching; +import graphql.GraphQLError; +import graphql.language.SourceLocation; + +import java.util.List; +import java.util.Map; + +public class GraphQLErrorAdapter implements GraphQLError { + + private GraphQLError error; + + public GraphQLErrorAdapter(GraphQLError error) { + this.error = error; + } + + @Override + public Map getExtensions() { + return error.getExtensions(); + } + + @Override + public List getLocations() { + return error.getLocations(); + } + + @Override + public ErrorType getErrorType() { + return error.getErrorType(); + } + + @Override + public List getPath() { + return error.getPath(); + } + + @Override + public Map toSpecification() { + return error.toSpecification(); + } + + @Override + public String getMessage() { + return (error instanceof ExceptionWhileDataFetching) ? ((ExceptionWhileDataFetching) error).getException().getMessage() : error.getMessage(); + } +} \ No newline at end of file diff --git a/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/exception/InvalidInputException.java b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/exception/InvalidInputException.java new file mode 100644 index 0000000000..78c8e83e27 --- /dev/null +++ b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/exception/InvalidInputException.java @@ -0,0 +1,7 @@ +package com.baeldung.graphql.error.handling.exception; + +public class InvalidInputException extends RuntimeException { + public InvalidInputException(String message) { + super(message); + } +} diff --git a/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/exception/VehicleAlreadyPresentException.java b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/exception/VehicleAlreadyPresentException.java new file mode 100644 index 0000000000..8f6f0ce615 --- /dev/null +++ b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/exception/VehicleAlreadyPresentException.java @@ -0,0 +1,14 @@ +package com.baeldung.graphql.error.handling.exception; + +import java.util.Map; + +public class VehicleAlreadyPresentException extends AbstractGraphQLException { + + public VehicleAlreadyPresentException(String message) { + super(message); + } + + public VehicleAlreadyPresentException(String message, Map additionParams) { + super(message, additionParams); + } +} diff --git a/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/exception/VehicleNotFoundException.java b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/exception/VehicleNotFoundException.java new file mode 100644 index 0000000000..0d2ad8c597 --- /dev/null +++ b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/exception/VehicleNotFoundException.java @@ -0,0 +1,14 @@ +package com.baeldung.graphql.error.handling.exception; + +import java.util.Map; + +public class VehicleNotFoundException extends AbstractGraphQLException { + + public VehicleNotFoundException(String message) { + super(message); + } + + public VehicleNotFoundException(String message, Map params) { + super(message, params); + } +} diff --git a/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/repository/InventoryRepository.java b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/repository/InventoryRepository.java new file mode 100644 index 0000000000..f4a0043408 --- /dev/null +++ b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/repository/InventoryRepository.java @@ -0,0 +1,9 @@ +package com.baeldung.graphql.error.handling.repository; + +import com.baeldung.graphql.error.handling.domain.Vehicle; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface InventoryRepository extends JpaRepository { +} diff --git a/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/repository/LocationRepository.java b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/repository/LocationRepository.java new file mode 100644 index 0000000000..716b8d3ef3 --- /dev/null +++ b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/repository/LocationRepository.java @@ -0,0 +1,9 @@ +package com.baeldung.graphql.error.handling.repository; + +import com.baeldung.graphql.error.handling.domain.Location; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface LocationRepository extends JpaRepository { +} diff --git a/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/resolver/Mutation.java b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/resolver/Mutation.java new file mode 100644 index 0000000000..8463ebf8eb --- /dev/null +++ b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/resolver/Mutation.java @@ -0,0 +1,20 @@ +package com.baeldung.graphql.error.handling.resolver; + +import com.baeldung.graphql.error.handling.domain.Location; +import com.baeldung.graphql.error.handling.domain.Vehicle; +import com.baeldung.graphql.error.handling.service.InventoryService; +import com.coxautodev.graphql.tools.GraphQLMutationResolver; +import org.springframework.stereotype.Component; + +@Component +public class Mutation implements GraphQLMutationResolver { + private InventoryService inventoryService; + + public Mutation(InventoryService inventoryService) { + this.inventoryService = inventoryService; + } + + public Vehicle addVehicle(String vin, Integer year, String make, String model, String trim, Location location) { + return this.inventoryService.addVehicle(vin, year, make, model, trim, location); + } +} diff --git a/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/resolver/Query.java b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/resolver/Query.java new file mode 100644 index 0000000000..ece018464a --- /dev/null +++ b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/resolver/Query.java @@ -0,0 +1,29 @@ +package com.baeldung.graphql.error.handling.resolver; + +import com.baeldung.graphql.error.handling.domain.Vehicle; +import com.baeldung.graphql.error.handling.service.InventoryService; +import com.coxautodev.graphql.tools.GraphQLQueryResolver; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class Query implements GraphQLQueryResolver { + private final InventoryService inventoryService; + + public Query(InventoryService inventoryService) { + this.inventoryService = inventoryService; + } + + public List searchAll() { + return this.inventoryService.searchAll(); + } + + public List searchByLocation(String zipcode) { + return this.inventoryService.searchByLocation(zipcode); + } + + public Vehicle searchByVin(String vin) { + return this.inventoryService.searchByVin(vin); + } +} diff --git a/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/service/InventoryService.java b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/service/InventoryService.java new file mode 100644 index 0000000000..7064b08760 --- /dev/null +++ b/graphql/graphql-error-handling/src/main/java/com/baeldung/graphql/error/handling/service/InventoryService.java @@ -0,0 +1,67 @@ +package com.baeldung.graphql.error.handling.service; + +import com.baeldung.graphql.error.handling.domain.Location; +import com.baeldung.graphql.error.handling.domain.Vehicle; +import com.baeldung.graphql.error.handling.exception.InvalidInputException; +import com.baeldung.graphql.error.handling.exception.VehicleAlreadyPresentException; +import com.baeldung.graphql.error.handling.exception.VehicleNotFoundException; +import com.baeldung.graphql.error.handling.repository.InventoryRepository; +import com.baeldung.graphql.error.handling.repository.LocationRepository; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import javax.transaction.Transactional; +import java.util.*; + +@Service +public class InventoryService { + private InventoryRepository inventoryRepository; + private LocationRepository locationRepository; + + public InventoryService(InventoryRepository inventoryRepository, LocationRepository locationRepository) { + this.inventoryRepository = inventoryRepository; + this.locationRepository = locationRepository; + } + + @Transactional + public Vehicle addVehicle(String vin, Integer year, String make, String model, String trim, Location location) { + Optional existingVehicle = this.inventoryRepository.findById(vin); + if (existingVehicle.isPresent()) { + Map params = new HashMap<>(); + params.put("vin", vin); + throw new VehicleAlreadyPresentException("Failed to add vehicle. Vehicle with vin " + vin + " already present.", params); + } + Vehicle vehicle = Vehicle.builder() + .vin(vin) + .year(year) + .make(make) + .model(model) + .location(location) + .trim(trim) + .build(); + + this.locationRepository.save(location); + return this.inventoryRepository.save(vehicle); + } + + public List searchAll() { + return this.inventoryRepository.findAll(); + } + + public List searchByLocation(String zipcode) { + if (StringUtils.isEmpty(zipcode) || zipcode.length() != 5) { + throw new InvalidInputException("Invalid zipcode " + zipcode + " provided."); + } + return this.locationRepository.findById(zipcode) + .map(Location::getVehicles) + .orElse(new ArrayList<>()); + } + + public Vehicle searchByVin(String vin) { + return this.inventoryRepository.findById(vin).orElseThrow(() -> { + Map params = new HashMap<>(); + params.put("vin", vin); + return new VehicleNotFoundException("Vehicle with vin " + vin + " not found.", params); + }); + } +} diff --git a/graphql/graphql-error-handling/src/main/resources/application.yml b/graphql/graphql-error-handling/src/main/resources/application.yml new file mode 100644 index 0000000000..155e133a62 --- /dev/null +++ b/graphql/graphql-error-handling/src/main/resources/application.yml @@ -0,0 +1,23 @@ +graphql: + servlet: + mapping: /graphql + +spring: + datasource: + url: "jdbc:h2:mem:graphqldb" + driverClassName: "org.h2.Driver" + username: sa + password: + + initialization-mode: always + platform: h2 + + jpa: + show-sql: true + properties: + hibernate: + dialect: org.hibernate.dialect.H2Dialect + ddl-auto: none + + h2: + console.enabled: true \ No newline at end of file diff --git a/graphql/graphql-error-handling/src/main/resources/graphql/inventory.graphqls b/graphql/graphql-error-handling/src/main/resources/graphql/inventory.graphqls new file mode 100644 index 0000000000..7dcb5403c1 --- /dev/null +++ b/graphql/graphql-error-handling/src/main/resources/graphql/inventory.graphqls @@ -0,0 +1,23 @@ +type Vehicle { + vin: ID! + year: Int! + make: String! + model: String! + trim: String! +} + +input Location { + city: String + state: String + zipcode: String! +} + +type Query { + searchAll: [Vehicle]! + searchByLocation(zipcode: String!): [Vehicle]! + searchByVin(vin: String!): Vehicle +} + +type Mutation { + addVehicle(vin: ID!, year: Int!, make: String!, model: String!, trim: String, location: Location): Vehicle! +} \ No newline at end of file diff --git a/graphql/graphql-error-handling/src/main/resources/import.sql b/graphql/graphql-error-handling/src/main/resources/import.sql new file mode 100644 index 0000000000..62907a86c3 --- /dev/null +++ b/graphql/graphql-error-handling/src/main/resources/import.sql @@ -0,0 +1,7 @@ +insert into LOCATION values('07092', 'Mountainside', 'NJ'); +insert into LOCATION values ('94118', 'San Francisco', 'CA'); +insert into LOCATION values ('10002', 'New York', 'NY'); + +insert into VEHICLE (vin, year, make, model, trim, fk_location) values('KM8JN72DX7U587496', 2007, 'Hyundai', 'Tucson', null, '07092'); +insert into VEHICLE (vin, year, make, model, trim, fk_location) values('JTKKU4B41C1023346', 2012, 'Toyota', 'Scion', 'Xd', '94118'); +insert into VEHICLE (vin, year, make, model, trim, fk_location) values('1G1JC1444PZ215071', 2000, 'Chevrolet', 'CAVALIER VL', 'RS', '07092'); \ No newline at end of file diff --git a/graphql/graphql-error-handling/src/test/java/com/baeldung/graphql/error/handling/GraphQLErrorHandlerApplicationIntegrationTest.java b/graphql/graphql-error-handling/src/test/java/com/baeldung/graphql/error/handling/GraphQLErrorHandlerApplicationIntegrationTest.java new file mode 100644 index 0000000000..069a08ce02 --- /dev/null +++ b/graphql/graphql-error-handling/src/test/java/com/baeldung/graphql/error/handling/GraphQLErrorHandlerApplicationIntegrationTest.java @@ -0,0 +1,55 @@ +package com.baeldung.graphql.error.handling; + +import com.graphql.spring.boot.test.GraphQLResponse; +import com.graphql.spring.boot.test.GraphQLTestTemplate; +import org.json.JSONException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.junit4.SpringRunner; + +import java.io.IOException; + +import static com.baeldung.graphql.error.handling.TestUtils.readFile; +import static java.lang.String.format; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = GraphQLErrorHandlerApplication.class) +public class GraphQLErrorHandlerApplicationIntegrationTest { + + @Autowired + private GraphQLTestTemplate graphQLTestTemplate; + + private static final String GRAPHQL_TEST_REQUEST_PATH = "graphql/request/%s.graphql"; + private static final String GRAPHQL_TEST_RESPONSE_PATH = "graphql/response/%s.json"; + + @Test + public void whenUnknownOperation_thenRespondWithRequestError() throws IOException, JSONException { + String graphqlName = "request_error_unknown_operation"; + GraphQLResponse actualResponse = graphQLTestTemplate.postForResource(format(GRAPHQL_TEST_REQUEST_PATH, graphqlName)); + String expectedResponse = readFile(format(GRAPHQL_TEST_RESPONSE_PATH, graphqlName)); + + JSONAssert.assertEquals(expectedResponse, actualResponse.getRawResponse().getBody(), true); + } + + @Test + public void whenInvalidSyntaxRequest_thenRespondWithRequestError() throws IOException, JSONException { + String graphqlName = "request_error_invalid_request_syntax"; + GraphQLResponse actualResponse = graphQLTestTemplate.postForResource(format(GRAPHQL_TEST_REQUEST_PATH, graphqlName)); + String expectedResponse = readFile(format(GRAPHQL_TEST_RESPONSE_PATH, graphqlName)); + + JSONAssert.assertEquals(expectedResponse, actualResponse.getRawResponse().getBody(), true); + } + + @Test + public void whenRequestAllNonNullField_thenRespondPartialDataWithFieldError() throws IOException, JSONException { + String graphqlName = "field_error_request_non_null_fields_partial_response"; + GraphQLResponse actualResponse = graphQLTestTemplate.postForResource(format(GRAPHQL_TEST_REQUEST_PATH, graphqlName)); + String expectedResponse = readFile(format(GRAPHQL_TEST_RESPONSE_PATH, graphqlName)); + + JSONAssert.assertEquals(expectedResponse, actualResponse.getRawResponse().getBody(), true); + } +} diff --git a/graphql/graphql-error-handling/src/test/java/com/baeldung/graphql/error/handling/TestUtils.java b/graphql/graphql-error-handling/src/test/java/com/baeldung/graphql/error/handling/TestUtils.java new file mode 100644 index 0000000000..557f1d9c91 --- /dev/null +++ b/graphql/graphql-error-handling/src/test/java/com/baeldung/graphql/error/handling/TestUtils.java @@ -0,0 +1,15 @@ +package com.baeldung.graphql.error.handling; + +import org.apache.commons.io.IOUtils; +import org.springframework.core.io.ClassPathResource; + +import java.io.IOException; +import java.nio.charset.Charset; + +public class TestUtils { + public static String readFile(String path) throws IOException { + return IOUtils.toString( + new ClassPathResource(path).getInputStream(), Charset.defaultCharset() + ); + } +} diff --git a/graphql/graphql-error-handling/src/test/resources/graphql/request/field_error_request_non_null_fields_partial_response.graphql b/graphql/graphql-error-handling/src/test/resources/graphql/request/field_error_request_non_null_fields_partial_response.graphql new file mode 100644 index 0000000000..17affc50cb --- /dev/null +++ b/graphql/graphql-error-handling/src/test/resources/graphql/request/field_error_request_non_null_fields_partial_response.graphql @@ -0,0 +1,10 @@ +# trim is non null but one of the record has null value for trim +query { + searchAll { + vin + year + make + model + trim + } +} \ No newline at end of file diff --git a/graphql/graphql-error-handling/src/test/resources/graphql/request/request_error_invalid_request_syntax.graphql b/graphql/graphql-error-handling/src/test/resources/graphql/request/request_error_invalid_request_syntax.graphql new file mode 100644 index 0000000000..98920eb17a --- /dev/null +++ b/graphql/graphql-error-handling/src/test/resources/graphql/request/request_error_invalid_request_syntax.graphql @@ -0,0 +1,9 @@ +query { + searchByVin(vin: "error) { + vin + year + make + model + trim + } +} \ No newline at end of file diff --git a/graphql/graphql-error-handling/src/test/resources/graphql/request/request_error_unknown_operation.graphql b/graphql/graphql-error-handling/src/test/resources/graphql/request/request_error_unknown_operation.graphql new file mode 100644 index 0000000000..fb6c3d1039 --- /dev/null +++ b/graphql/graphql-error-handling/src/test/resources/graphql/request/request_error_unknown_operation.graphql @@ -0,0 +1,9 @@ +subscription { + searchByVin(vin: "75024") { + vin + year + make + model + trim + } +} \ No newline at end of file diff --git a/graphql/graphql-error-handling/src/test/resources/graphql/response/field_error_request_non_null_fields_partial_response.json b/graphql/graphql-error-handling/src/test/resources/graphql/response/field_error_request_non_null_fields_partial_response.json new file mode 100644 index 0000000000..760190128e --- /dev/null +++ b/graphql/graphql-error-handling/src/test/resources/graphql/response/field_error_request_non_null_fields_partial_response.json @@ -0,0 +1,34 @@ +{ + "data": { + "searchAll": [ + null, + { + "vin": "JTKKU4B41C1023346", + "year": 2012, + "make": "Toyota", + "model": "Scion", + "trim": "Xd" + }, + { + "vin": "1G1JC1444PZ215071", + "year": 2000, + "make": "Chevrolet", + "model": "CAVALIER VL", + "trim": "RS" + } + ] + }, + "errors": [ + { + "message": "Cannot return null for non-nullable type: 'String' within parent 'Vehicle' (/searchAll[0]/trim)", + "path": [ + "searchAll", + 0, + "trim" + ], + "errorType": "DataFetchingException", + "locations": null, + "extensions": null + } + ] +} \ No newline at end of file diff --git a/graphql/graphql-error-handling/src/test/resources/graphql/response/request_error_invalid_request_syntax.json b/graphql/graphql-error-handling/src/test/resources/graphql/response/request_error_invalid_request_syntax.json new file mode 100644 index 0000000000..2835b42133 --- /dev/null +++ b/graphql/graphql-error-handling/src/test/resources/graphql/response/request_error_invalid_request_syntax.json @@ -0,0 +1,18 @@ +{ + "data": null, + "errors": [ + { + "message": "Invalid Syntax", + "locations": [ + { + "line": 5, + "column": 8, + "sourceName": null + } + ], + "errorType": "InvalidSyntax", + "path": null, + "extensions": null + } + ] +} \ No newline at end of file diff --git a/graphql/graphql-error-handling/src/test/resources/graphql/response/request_error_unknown_operation.json b/graphql/graphql-error-handling/src/test/resources/graphql/response/request_error_unknown_operation.json new file mode 100644 index 0000000000..b5872fc80f --- /dev/null +++ b/graphql/graphql-error-handling/src/test/resources/graphql/response/request_error_unknown_operation.json @@ -0,0 +1,18 @@ +{ + "data": null, + "errors": [ + { + "errorType": "OperationNotSupported", + "locations": [ + { + "line": 1, + "column": 1, + "sourceName": null + } + ], + "extensions": null, + "message": "Schema is not configured for subscriptions.", + "path": null + } + ] +} \ No newline at end of file diff --git a/graphql/graphql-error-handling/src/test/resources/init_script.sql b/graphql/graphql-error-handling/src/test/resources/init_script.sql new file mode 100644 index 0000000000..e69de29bb2