Search in sources :

Example 6 with Resource

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");
        }
    }
}
Also used : Resource(io.github.microcks.domain.Resource) Service(io.github.microcks.domain.Service) IOException(java.io.IOException) Operation(io.github.microcks.domain.Operation) MockRepositoryImportException(io.github.microcks.util.MockRepositoryImportException) Test(org.junit.Test)

Example 7 with Resource

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;
}
Also used : TestReturn(io.github.microcks.domain.TestReturn) ArrayList(java.util.ArrayList) DynamicMessage(com.google.protobuf.DynamicMessage) CallOptions(io.grpc.CallOptions) URL(java.net.URL) JsonFormat(com.google.protobuf.util.JsonFormat) ManagedChannel(io.grpc.ManagedChannel) Descriptors(com.google.protobuf.Descriptors) TlsChannelCredentials(io.grpc.TlsChannelCredentials) Resource(io.github.microcks.domain.Resource) Request(io.github.microcks.domain.Request) InvalidProtocolBufferException(com.google.protobuf.InvalidProtocolBufferException) X509Certificate(java.security.cert.X509Certificate) URISyntaxException(java.net.URISyntaxException) InvalidProtocolBufferException(com.google.protobuf.InvalidProtocolBufferException) IOException(java.io.IOException) Response(io.github.microcks.domain.Response) ByteArrayInputStream(java.io.ByteArrayInputStream) X509TrustManager(javax.net.ssl.X509TrustManager)

Example 8 with Resource

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;
}
Also used : Response(io.github.microcks.domain.Response) ClientHttpResponse(org.springframework.http.client.ClientHttpResponse) Resource(io.github.microcks.domain.Resource) JsonNode(com.fasterxml.jackson.databind.JsonNode) IOException(java.io.IOException)

Example 9 with Resource

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);
}
Also used : HttpHeaders(org.springframework.http.HttpHeaders) TypeDefinitionRegistry(graphql.schema.idl.TypeDefinitionRegistry) ArrayList(java.util.ArrayList) JsonNode(com.fasterxml.jackson.databind.JsonNode) GraphQLHttpRequest(io.github.microcks.util.graphql.GraphQLHttpRequest) JsonProcessingException(com.fasterxml.jackson.core.JsonProcessingException) ObjectNode(com.fasterxml.jackson.databind.node.ObjectNode) GraphQL(graphql.GraphQL) Resource(io.github.microcks.domain.Resource) Service(io.github.microcks.domain.Service) ExecutionResult(graphql.ExecutionResult) GraphQLSchema(graphql.schema.GraphQLSchema) JsonMappingException(io.github.microcks.util.dispatcher.JsonMappingException) JsonProcessingException(com.fasterxml.jackson.core.JsonProcessingException) ResponseEntity(org.springframework.http.ResponseEntity) Header(io.github.microcks.domain.Header) RequestMapping(org.springframework.web.bind.annotation.RequestMapping)

Example 10 with Resource

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;
}
Also used : Resource(io.github.microcks.domain.Resource) ArrayList(java.util.ArrayList)

Aggregations

Resource (io.github.microcks.domain.Resource)19 Service (io.github.microcks.domain.Service)11 IOException (java.io.IOException)11 MockRepositoryImportException (io.github.microcks.util.MockRepositoryImportException)8 Test (org.junit.Test)7 ArrayList (java.util.ArrayList)6 Operation (io.github.microcks.domain.Operation)5 JsonNode (com.fasterxml.jackson.databind.JsonNode)4 Response (io.github.microcks.domain.Response)4 Request (io.github.microcks.domain.Request)3 HttpHeaders (org.springframework.http.HttpHeaders)3 ResponseEntity (org.springframework.http.ResponseEntity)3 RequestMapping (org.springframework.web.bind.annotation.RequestMapping)3 ObjectNode (com.fasterxml.jackson.databind.node.ObjectNode)2 File (java.io.File)2 ClassPathResource (org.springframework.core.io.ClassPathResource)2 JsonProcessingException (com.fasterxml.jackson.core.JsonProcessingException)1 Descriptors (com.google.protobuf.Descriptors)1 DynamicMessage (com.google.protobuf.DynamicMessage)1 InvalidProtocolBufferException (com.google.protobuf.InvalidProtocolBufferException)1