use of io.github.microcks.domain.Resource in project microcks by microcks.
the class GraphQLImporterTest method testSimpleGraphQLImport.
@Test
public void testSimpleGraphQLImport() {
GraphQLImporter importer = null;
try {
importer = new GraphQLImporter("target/test-classes/io/github/microcks/util/graphql/films.graphql");
} catch (IOException ioe) {
fail("Exception should not be thrown");
}
// Check that basic service properties are there.
List<Service> services = null;
try {
services = importer.getServiceDefinitions();
} catch (MockRepositoryImportException e) {
fail("Service definition import should not fail");
}
assertEquals(1, services.size());
Service service = services.get(0);
assertEquals("Movie Graph API", service.getName());
assertEquals(ServiceType.GRAPHQL, service.getType());
assertEquals("1.0", service.getVersion());
// Check that resources have been parsed, correctly renamed, etc...
List<Resource> resources = null;
try {
resources = importer.getResourceDefinitions(service);
} catch (MockRepositoryImportException mrie) {
fail("Resource definition import should not fail");
}
assertEquals(1, resources.size());
Resource resource = resources.get(0);
assertEquals(ResourceType.GRAPHQL_SCHEMA, resource.getType());
assertEquals("Movie Graph API-1.0.graphql", resource.getName());
// Check that operations and input/output have been found.
assertEquals(4, service.getOperations().size());
for (Operation operation : service.getOperations()) {
if ("allFilms".equals(operation.getName())) {
assertEquals("QUERY", operation.getMethod());
assertEquals("FilmsConnection", operation.getOutputName());
assertNull(operation.getDispatcher());
} else if ("film".equals(operation.getName())) {
assertEquals("QUERY", operation.getMethod());
assertEquals("Film", operation.getOutputName());
assertEquals("String", operation.getInputName());
assertEquals(DispatchStyles.QUERY_ARGS, operation.getDispatcher());
assertEquals("id", operation.getDispatcherRules());
} else if ("addStar".equals(operation.getName())) {
assertEquals("MUTATION", operation.getMethod());
assertEquals("Film", operation.getOutputName());
assertEquals("String", operation.getInputName());
assertEquals(DispatchStyles.QUERY_ARGS, operation.getDispatcher());
assertEquals("filmId", operation.getDispatcherRules());
} else if ("addReview".equals(operation.getName())) {
assertEquals("MUTATION", operation.getMethod());
assertEquals("Film", operation.getOutputName());
assertEquals("String, Review", operation.getInputName());
assertNull(operation.getDispatcher());
} else {
fail("Unknown operation");
}
}
}
use of io.github.microcks.domain.Resource in project microcks by microcks.
the class GrpcTestRunner method runTest.
@Override
public List<TestReturn> runTest(Service service, Operation operation, TestResult testResult, List<Request> requests, String endpointUrl, HttpMethod method) throws URISyntaxException, IOException {
log.debug("Launching test run on {} for {} request(s)", endpointUrl, requests.size());
if (requests.isEmpty()) {
return null;
}
// Initialize results.
List<TestReturn> results = new ArrayList<>();
// Rebuild the GRPC fullMethodName.
String fullMethodName = service.getXmlNS() + "." + service.getName() + "/" + operation.getName();
// Build a new GRPC Channel from endpoint URL.
URL endpoint = new URL(endpointUrl);
ManagedChannel channel;
if (endpointUrl.startsWith("https://") || endpoint.getPort() == 443) {
TlsChannelCredentials.Builder tlsBuilder = TlsChannelCredentials.newBuilder();
if (secret != null && secret.getCaCertPem() != null) {
// Install a trust manager with custom CA certificate.
tlsBuilder.trustManager(new ByteArrayInputStream(secret.getCaCertPem().getBytes(StandardCharsets.UTF_8)));
} else {
// Install a trust manager that accepts everything and does not validate certificate chains.
tlsBuilder.trustManager(new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
} });
}
// Build a Channel using the TLS Builder.
channel = Grpc.newChannelBuilderForAddress(endpoint.getHost(), endpoint.getPort(), tlsBuilder.build()).build();
} else {
// Build a simple Channel using plain text.
channel = Grpc.newChannelBuilderForAddress(endpoint.getHost(), endpoint.getPort(), null).usePlaintext().build();
}
// In order to produce outgoing byte array, we need the Protobuf binary descriptor that should
// have been processed while importing the .proto schema for the service.
List<Resource> resources = resourceRepository.findByServiceIdAndType(service.getId(), ResourceType.PROTOBUF_DESCRIPTOR);
if (resources == null || resources.size() != 1) {
log.error("Could not found any pre-processed Protobuf binary descriptor...");
results.add(new TestReturn(TestReturn.FAILURE_CODE, 0, "Could not found any pre-processed Protobuf binary descriptor...", null, null));
return results;
}
Resource pbResource = resources.get(0);
Descriptors.MethodDescriptor md = null;
try {
md = GrpcUtil.findMethodDescriptor(pbResource.getContent(), service.getName(), operation.getName());
} catch (Exception e) {
log.error("Protobuf descriptor cannot be read or parsed: " + e.getMessage());
results.add(new TestReturn(TestReturn.FAILURE_CODE, 0, "Protobuf descriptor cannot be read or parsed: " + e.getMessage(), null, null));
return results;
}
// Use a builder for out type with a Json parser to merge content and build outMsg.
DynamicMessage.Builder reqBuilder = DynamicMessage.newBuilder(md.getInputType());
DynamicMessage.Builder resBuilder = DynamicMessage.newBuilder(md.getOutputType());
JsonFormat.Parser parser = JsonFormat.parser();
JsonFormat.Printer printer = JsonFormat.printer();
for (Request request : requests) {
// Reset status code, message and request each time.
int code = TestReturn.SUCCESS_CODE;
String message = null;
reqBuilder.clear();
resBuilder.clear();
// Now produce the request message byte array.
parser.merge(request.getContent(), reqBuilder);
byte[] requestBytes = reqBuilder.build().toByteArray();
CallOptions callOptions = CallOptions.DEFAULT.withDeadline(Deadline.after(timeout, TimeUnit.MILLISECONDS));
if (secret != null && secret.getToken() != null) {
log.debug("Secret contains token and maybe token header, adding them as call credentials");
callOptions.withCallCredentials(new TokenCallCredentials(secret.getToken(), secret.getTokenHeader()));
}
// Actually execute request.
long startTime = System.currentTimeMillis();
byte[] responseBytes = ClientCalls.blockingUnaryCall(channel, GrpcUtil.buildGenericUnaryMethodDescriptor(fullMethodName), callOptions, requestBytes);
long duration = System.currentTimeMillis() - startTime;
// Create a Response object for returning.
Response response = new Response();
response.setStatus("200");
response.setMediaType("application/x-protobuf");
response.setContent(new String(responseBytes, "UTF-8"));
try {
// Validate incoming message parsing a DynamicMessage.
DynamicMessage respMsg = DynamicMessage.parseFrom(md.getOutputType(), responseBytes);
// Now update response content with readable content.
String respJson = printer.print(respMsg);
response.setContent(respJson);
results.add(new TestReturn(code, duration, message, request, response));
} catch (InvalidProtocolBufferException ipbe) {
log.error("Received bytes cannot be transformed in " + md.getOutputType().getFullName());
results.add(new TestReturn(TestReturn.FAILURE_CODE, duration, "Received bytes cannot be transformed in \" + md.getOutputType().getFullName()", request, response));
}
}
return results;
}
use of io.github.microcks.domain.Resource in project microcks by microcks.
the class OpenAPITestRunner method extractTestReturnCode.
@Override
protected int extractTestReturnCode(Service service, Operation operation, Request request, ClientHttpResponse httpResponse, String responseContent) {
int code = TestReturn.SUCCESS_CODE;
int responseCode = 0;
try {
responseCode = httpResponse.getRawStatusCode();
log.debug("Response status code : " + responseCode);
} catch (IOException ioe) {
log.debug("IOException while getting raw status code in response", ioe);
return TestReturn.FAILURE_CODE;
}
// Extract response content-type in any case.
String contentType = null;
if (httpResponse.getHeaders().getContentType() != null) {
log.debug("Response media-type is {}", httpResponse.getHeaders().getContentType().toString());
contentType = httpResponse.getHeaders().getContentType().toString();
// Sanitize charset information from media-type.
if (contentType.contains("charset=") && contentType.indexOf(";") > 0) {
contentType = contentType.substring(0, contentType.indexOf(";"));
}
}
// If required, compare response code and content-type to expected ones.
if (validateResponseCode) {
Response expectedResponse = responseRepository.findById(request.getResponseId()).orElse(null);
log.debug("Response expected status code : " + expectedResponse.getStatus());
if (!String.valueOf(responseCode).equals(expectedResponse.getStatus())) {
log.debug("Response HttpStatus does not match expected one, returning failure");
return TestReturn.FAILURE_CODE;
}
if (!expectedResponse.getMediaType().equalsIgnoreCase(contentType)) {
log.debug("Response Content-Type does not match expected one, returning failure");
}
}
// Alternatives schemes are on their way for OpenAPI but not yet ready (see https://github.com/OAI/OpenAPI-Specification/pull/1736)
if (responseCode != 204 && APPLICATION_JSON_TYPE.equals(contentType)) {
boolean isOpenAPIv3 = true;
// Retrieve the resource corresponding to OpenAPI specification if any.
Resource openapiSpecResource = null;
List<Resource> resources = resourceRepository.findByServiceId(service.getId());
for (Resource resource : resources) {
if (ResourceType.OPEN_API_SPEC.equals(resource.getType())) {
openapiSpecResource = resource;
break;
} else if (ResourceType.SWAGGER.equals(resource.getType())) {
openapiSpecResource = resource;
isOpenAPIv3 = false;
break;
}
}
if (openapiSpecResource == null) {
log.debug("Found no OpenAPI specification resource for service {0}, so failing validating", service.getId());
return TestReturn.FAILURE_CODE;
}
JsonNode openApiSpec = null;
try {
openApiSpec = OpenAPISchemaValidator.getJsonNodeForSchema(openapiSpecResource.getContent());
} catch (IOException ioe) {
log.debug("OpenAPI specification cannot be transformed into valid JsonNode schema, so failing");
return TestReturn.FAILURE_CODE;
}
// Extract JsonNode corresponding to response.
String verb = operation.getName().split(" ")[0].toLowerCase();
String path = operation.getName().split(" ")[1].trim();
// Get body content as a string.
JsonNode contentNode = null;
try {
contentNode = OpenAPISchemaValidator.getJsonNode(responseContent);
} catch (IOException ioe) {
log.debug("Response body cannot be accessed or transformed as Json, returning failure");
return TestReturn.FAILURE_CODE;
}
String jsonPointer = "/paths/" + path.replace("/", "~1") + "/" + verb + "/responses/" + responseCode;
if (isOpenAPIv3) {
lastValidationErrors = OpenAPISchemaValidator.validateJsonMessage(openApiSpec, contentNode, jsonPointer, contentType, resourceUrl);
} else {
lastValidationErrors = SwaggerSchemaValidator.validateJsonMessage(openApiSpec, contentNode, jsonPointer, resourceUrl);
}
if (!lastValidationErrors.isEmpty()) {
log.debug("OpenAPI schema validation errors found " + lastValidationErrors.size() + ", marking test as failed.");
return TestReturn.FAILURE_CODE;
}
log.debug("OpenAPI schema validation of response is successful !");
}
return code;
}
use of io.github.microcks.domain.Resource in project microcks by microcks.
the class GraphQLController method execute.
@RequestMapping(value = "/{service}/{version}/**", method = { RequestMethod.GET, RequestMethod.POST })
public ResponseEntity<?> execute(@PathVariable("service") String serviceName, @PathVariable("version") String version, @RequestParam(value = "delay", required = false) Long delay, @RequestBody(required = false) String body, HttpServletRequest request) {
log.info("Servicing GraphQL mock response for service [{}, {}]", serviceName, version);
log.debug("Request body: {}", body);
long startTime = System.currentTimeMillis();
// If serviceName was encoded with '+' instead of '%20', remove them.
if (serviceName.contains("+")) {
serviceName = serviceName.replace('+', ' ');
}
Service service = serviceRepository.findByNameAndVersion(serviceName, version);
GraphQLHttpRequest graphqlHttpReq;
Document graphqlRequest;
try {
graphqlHttpReq = GraphQLHttpRequest.from(body, request);
graphqlRequest = requestParser.parseDocument(graphqlHttpReq.getQuery());
} catch (Exception e) {
log.error("Error parsing GraphQL request: {}", e);
return new ResponseEntity<Object>("Error parsing GraphQL request: " + e.getMessage(), HttpStatus.BAD_REQUEST);
}
Definition graphqlDef = graphqlRequest.getDefinitions().get(0);
OperationDefinition graphqlOperation = (OperationDefinition) graphqlDef;
log.debug("Got this graphqlOperation: {}", graphqlOperation);
// Operation type is direct but name depends on syntax...
String operationName = graphqlHttpReq.getOperationName();
String operationType = graphqlOperation.getOperation().toString();
// Check is it's an introspection query to handle first.
if ("QUERY".equals(operationType) && INTROSPECTION_SELECTION.equals(((Field) graphqlOperation.getSelectionSet().getSelections().get(0)).getName())) {
log.info("Handling GraphQL schema introspection query...");
Resource graphqlSchema = resourceRepository.findByServiceIdAndType(service.getId(), ResourceType.GRAPHQL_SCHEMA).get(0);
TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(graphqlSchema.getContent());
GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, RuntimeWiring.MOCKED_WIRING);
GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build();
ExecutionResult executionResult = graphQL.execute(graphqlHttpReq.getQuery());
String responseContent = null;
try {
responseContent = mapper.writeValueAsString(executionResult);
} catch (JsonProcessingException jpe) {
log.error("Unknown Json processing exception", jpe);
return new ResponseEntity<>(jpe.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<Object>(responseContent, HttpStatus.OK);
}
// Then deal with one or many regular GraphQL selection queries.
List<GraphQLQueryResponse> graphqlResponses = new ArrayList<>();
final Long[] maxDelay = { (delay == null ? 0L : delay) };
for (Selection selection : graphqlOperation.getSelectionSet().getSelections()) {
GraphQLQueryResponse graphqlResponse = null;
try {
graphqlResponse = processGraphQLQuery(service, operationType, (Field) selection, graphqlRequest.getDefinitionsOfType(FragmentDefinition.class), body, graphqlHttpReq, request);
graphqlResponses.add(graphqlResponse);
if (graphqlResponse.getOperationDelay() != null && graphqlResponse.getOperationDelay() > maxDelay[0]) {
maxDelay[0] = graphqlResponse.getOperationDelay();
}
} catch (GraphQLQueryProcessingException e) {
log.error("Caught a GraphQL processing exception", e);
return new ResponseEntity<Object>(e.getMessage(), e.getStatus());
}
}
/* Optimized parallel version but need to better handle exception.
graphqlResponses = graphqlOperation.getSelectionSet().getSelections().stream().parallel().map(selection -> {
try {
GraphQLQueryResponse graphqlResponse = processGraphQLQuery(service, operationType, (Field) selection,
graphqlRequest.getDefinitionsOfType(FragmentDefinition.class), body, graphqlHttpReq, request);
if (graphqlResponse.getOperationDelay() != null && graphqlResponse.getOperationDelay() > maxDelay[0]) {
maxDelay[0] = graphqlResponse.getOperationDelay();
}
return graphqlResponse;
} catch (GraphQLQueryProcessingException e) {
log.error("Caught a GraphQL processing exception", e);
return null;
}
}).collect(Collectors.toList());
*/
// Deal with response headers.
HttpHeaders responseHeaders = new HttpHeaders();
for (GraphQLQueryResponse response : graphqlResponses) {
if (response.getResponse().getHeaders() != null) {
for (Header header : response.getResponse().getHeaders()) {
if (!HttpHeaders.TRANSFER_ENCODING.equalsIgnoreCase(header.getName())) {
responseHeaders.put(header.getName(), new ArrayList<>(header.getValues()));
}
}
}
}
if (!responseHeaders.containsKey("Content-Type") && !responseHeaders.containsKey("content-type")) {
responseHeaders.put("Content-Type", List.of("application/json"));
}
// Waiting for delay if any.
MockControllerCommons.waitForDelay(startTime, maxDelay[0]);
// Publish an invocation event before returning if enabled.
if (enableInvocationStats) {
MockControllerCommons.publishMockInvocation(applicationContext, this, service, graphqlResponses.get(0).getResponse(), startTime);
}
String responseContent = null;
JsonNode responseNode = graphqlResponses.get(0).getJsonResponse();
// If multi-queries and aliases were used, recompose an aggregated result.
if (graphqlResponses.size() > 1) {
ObjectNode aggregated = mapper.createObjectNode();
ObjectNode dataNode = aggregated.putObject("data");
for (GraphQLQueryResponse response : graphqlResponses) {
dataNode.set(response.getAlias(), response.getJsonResponse().path("data").path(response.getOperationName()).deepCopy());
}
responseNode = aggregated;
}
try {
responseContent = mapper.writeValueAsString(responseNode);
} catch (JsonProcessingException e) {
log.error("Unknown Json processing exception", e);
return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<Object>(responseContent, responseHeaders, HttpStatus.OK);
}
use of io.github.microcks.domain.Resource in project microcks by microcks.
the class GraphQLImporter method getResourceDefinitions.
@Override
public List<Resource> getResourceDefinitions(Service service) throws MockRepositoryImportException {
List<Resource> results = new ArrayList<>();
// Just one resource: The GraphQL schema file.
Resource graphqlSchema = new Resource();
graphqlSchema.setName(service.getName() + "-" + service.getVersion() + ".graphql");
graphqlSchema.setType(ResourceType.GRAPHQL_SCHEMA);
graphqlSchema.setContent(specContent);
results.add(graphqlSchema);
return results;
}
Aggregations