All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.davidmoten.odata.client.TestingService Maven / Gradle / Ivy

The newest version!
package com.github.davidmoten.odata.client;

import static java.util.Arrays.asList;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.fasterxml.jackson.core.JsonParseException;
import com.github.davidmoten.guavamini.Preconditions;

public final class TestingService {

    public static Builder baseUrl(String url) {
        if (url.endsWith("/")) {
            url = url.substring(0, url.length() - 1);
        }
        return new Builder().baseUrl(url);
    }

    public static Builder pathStyle(PathStyle pathStyle) {
        return new Builder().pathStyle(pathStyle);
    }

    public static Builder builder() {
        return new Builder();
    }

    public static final class Response {
        public final String resource;
        public final int statusCode;

        public Response(String resource, int statusCode) {
            this.resource = resource;
            this.statusCode = statusCode;
        }

        public Response(String text) {
            this(text, HttpURLConnection.HTTP_OK);
        }

        @Override
        public String toString() {
            return "Response [resource=" + resource + ", statusCode=" + statusCode + "]";
        }
    }

    public static abstract class BuilderBase, R> {
        final Map responses = new HashMap<>();
        final Map requests = new HashMap<>();

        String baseUrl = "https://testing.com";
        PathStyle pathStyle = PathStyle.IDENTIFIERS_AS_SEGMENTS;

        @SuppressWarnings("unchecked")
        public T baseUrl(String baseUrl) {
            this.baseUrl = baseUrl;
            return (T) this;
        }

        @SuppressWarnings("unchecked")
        public T pathStyle(PathStyle pathStyle) {
            this.pathStyle = pathStyle;
            return (T) this;
        }

        private String toUrl(String path) {
            final String url;
            if (path.startsWith("https://")) {
                url = path;
            } else {
                url = baseUrl + path;
            }
            return url;
        }

        public Builder expectRequest(String path) {
            return new Builder(this, path);
        }

        public static final class Builder, R> {

            private final List requestHeaders = new ArrayList<>();
            private final BuilderBase base;
            private String payloadResourcePath = "/empty.txt";
            private HttpMethod method = HttpMethod.GET;
            private final String path;
            private String responseResourcePath;
            private Integer statusCode;

            public Builder(BuilderBase base, String path) {
                this.base = base;
                this.path = path;
            }

            public Builder withPayload(String resourcePath) {
                this.payloadResourcePath = resourcePath;
                return this;
            }

            public Builder withRequestHeaders(RequestHeader... requestHeaders) {
                this.requestHeaders.addAll(Arrays.asList(requestHeaders));
                return this;
            }

            public Builder withMethod(HttpMethod method) {
                this.method = method;
                return this;
            }

            public Builder withResponse(String resourcePath) {
                this.responseResourcePath = resourcePath;
                return this;
            }

            public Builder withResponseStatusCode(int statusCode) {
                this.statusCode = statusCode;
                return this;
            }

            public Builder withRequestHeadersStandard() {
                return withRequestHeaders(RequestHeader.ACCEPT_JSON_METADATA_MINIMAL,
                        RequestHeader.ODATA_VERSION);
            }

            private T add() {
                return base.expectRequestAndResponse(path, payloadResourcePath,
                        responseResourcePath, method, applyDefaultIfMissing(statusCode, method),
                        requestHeaders.toArray(new RequestHeader[] {}));
            }

            public R build() {
                return add().build();
            }

            @SuppressWarnings("unchecked")
            public Builder expectRequest(String path) {
                return (Builder) add().expectRequest(path);
            }
        }

        private static int applyDefaultIfMissing(Integer statusCode, HttpMethod method) {
            if (statusCode != null) {
                return statusCode;
            } else if (method == HttpMethod.PUT || method == HttpMethod.PATCH
                    || method == HttpMethod.DELETE) {
                return HttpURLConnection.HTTP_NO_CONTENT;
            } else {
                return HttpURLConnection.HTTP_OK;
            }
        }

        @SuppressWarnings("unchecked")
        private T expectRequestAndResponse(String path, String requestResourceName,
                String responseResourceName, HttpMethod method, int statusCode,
                RequestHeader... requestHeaders) {
            String url = toUrl(path);
            requests.put(toKey(method, url, asList(requestHeaders)), requestResourceName);
            responses.put(toKey(method, url, asList(requestHeaders)),
                    new Response(responseResourceName, statusCode));
            return (T) this;
        }

        @SuppressWarnings("unchecked")
        public T expectDelete(String path, int responseStatusCode,
                RequestHeader... requestHeaders) {
            String key = toKey(HttpMethod.DELETE, baseUrl + path, asList(requestHeaders));
            requests.put(key, "/empty.txt");
            responses.put(key, new Response(null, responseStatusCode));
            return (T) this;
        }

        public T expectDelete(String path, RequestHeader... requestHeaders) {
            return expectDelete(path, HttpURLConnection.HTTP_NO_CONTENT, requestHeaders);
        }

        private static String toKey(HttpMethod method, String url,
                Collection requestHeaders) {
            return method + "\n  " + url + "\n  " + requestHeaders.stream()
                    .map(x -> x.name() + "=" + x.value()).sorted().collect(Collectors.joining("|"));
        }

        private static void log(Object o) {
            System.out.println(o);
        }

        protected HttpService createService() {
            return new HttpService() {

                @Override
                public HttpResponse submit(HttpMethod method, String url,
                        List requestHeaders, InputStream content, int length,
                        HttpRequestOptions options) {
                    if (method == HttpMethod.GET) {
                        Preconditions.checkArgument(content == null);
                        return _get(url, requestHeaders, options);
                    } else {
                        return postPatchPutOrDelete(url, requestHeaders, content, length, options,
                                method);
                    }
                }

                private HttpResponse _get(String url, List requestHeaders,
                        HttpRequestOptions options) {
                    logExpected();
                    String key = BuilderBase.toKey(HttpMethod.GET, url, requestHeaders);
                    log("Getting:\n" + key);
                    Response response = responses.get(key);
                    String resourceName = response == null ? null : response.resource;
                    if (resourceName == null) {
                        throw new RuntimeException("GET response not found for url=" + url
                                + ", headers=" + requestHeaders);
                    }
                    try {
                        URL resource = TestingService.class.getResource(resourceName);
                        if (resource == null) {
                            throw new RuntimeException(
                                    "resource not found on classpath: " + resourceName);
                        }
                        byte[] bytes = Files.readAllBytes(Paths.get(resource.toURI()));
                        return new HttpResponse(response.statusCode, bytes);
                    } catch (IOException | URISyntaxException e) {
                        throw new RuntimeException(e);
                    }
                }

                private void logExpected() {
                    log("Expected responses:");
                    responses.entrySet().forEach(r -> log(r.getKey() + "\n=>" + r.getValue()));
                }

                private HttpResponse postPatchPutOrDelete(String url,
                        List requestHeaders, InputStream content, int length,
                        HttpRequestOptions options, HttpMethod method) {
                    log(method + " called at " + url);
                    final String text;
                    if (content == null) {
                        text = "";
                    } else {
                        text = Util.utf8(content);
                    }
                    log("content=" + text);
                    logExpected();
                    log("Calling:");
                    String key = BuilderBase.toKey(method, url, requestHeaders);
                    log(key);
                    String requestResourceName = requests.get(key);
                    if (requestResourceName == null) {
                        throw new RuntimeException(method + " request not expected for url=" + url
                                + ", headers=" + requestHeaders);
                    }
                    try {
                        System.out.println("requestResourceName=" + requestResourceName);
                        byte[] requestExpected = readResource(url, requestResourceName);
                        String expectedText = new String(requestExpected, StandardCharsets.UTF_8);
                        if (expectedText.equals(text) || jsonEquals(text, expectedText)) {
                            Response resp = responses
                                    .get(BuilderBase.toKey(method, url, requestHeaders));
                            String responseResourceName = resp.resource;
                            final byte[] responseExpected;
                            if (responseResourceName == null) {
                                responseExpected = null;
                            } else {
                                responseExpected = readResource(url, responseResourceName);
                            }
//                            final int responseCode;
//                            if (resp.statusCode != HttpURLConnection.HTTP_OK) {
//                                responseCode = resp.statusCode;
//                            } else {
//                                responseCode = url.contains("delta") ? HttpURLConnection.HTTP_OK
//                                        : HttpURLConnection.HTTP_CREATED;
//                            }
                            return new HttpResponse(resp.statusCode, responseExpected);
                        } else {
                            throw new RuntimeException(
                                    "request does not match expected.\n==== Received ====\n" + text
                                            + "\n==== Expected =====\n" + expectedText);
                        }
                    } catch (IOException | URISyntaxException e) {
                        throw new RuntimeException(e);
                    }
                }

                @Override
                public Path getBasePath() {
                    return new Path(baseUrl, pathStyle);
                }

                @Override
                public void close() throws Exception {
                    // do nothing
                }

                @Override
                public InputStream getStream(String url, List requestHeaders,
                        HttpRequestOptions options) {
                    HttpResponse h = get(url, requestHeaders, options);
                    return new ByteArrayInputStream(h.getBytes());
                }

                @Override
                public InputStream getStream(HttpMethod method, String url,
                        List requestHeaders, HttpRequestOptions options) {
                    throw new UnsupportedOperationException();
                }

            };
        }

        public abstract R build();
    }

    private static boolean jsonEquals(final String text, String expectedText) throws IOException {
        try {
            return Serializer.INSTANCE.matches(expectedText, text);
        } catch (JsonParseException e) {
            return false;
        }
    }

    public static abstract class ContainerBuilder extends BuilderBase, T> {

        public abstract T _create(Context context);

        private final Map properties = new HashMap<>();
        private final List schemas = new ArrayList<>();

        @Override
        public T build() {
            return _create(new Context(Serializer.INSTANCE, createService(), properties, schemas));
        }

        public ContainerBuilder addSchema(SchemaInfo schema) {
            this.schemas.add(schema);
            return this;
        }

        public ContainerBuilder addProperty(String name, Object value) {
            properties.put(name, value);
            return this;
        }

        public ContainerBuilder addProperties(Map p) {
            properties.putAll(p);
            return this;
        }

    }

    public static final class Builder extends BuilderBase {

        @Override
        public HttpService build() {
            return createService();
        }
    }

    private static byte[] readResource(String url, String resourceName)
            throws IOException, URISyntaxException {
        if (resourceName == null) {
            throw new RuntimeException("resource not found for url=" + url);
        }
        try (InputStream in = TestingService.class.getResourceAsStream(resourceName)) {
            return Util.toByteArray(in);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy