From 49e2d19eee6ef86a163709562a1339225d86495b Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Tue, 19 Sep 2023 12:44:08 +0200 Subject: [PATCH] feat: Mock Web Server migrated to this repository Signed-off-by: Marc Nuri --- CHANGELOG.md | 1 + .../crud/KubernetesCrudDispatcherHandler.java | 4 +- junit/mockwebserver/pom.xml | 72 ++ .../io/fabric8/mockwebserver/Context.java | 36 + .../mockwebserver/DefaultMockServer.java | 210 ++++++ .../io/fabric8/mockwebserver/MockServer.java | 107 +++ .../mockwebserver/MockServerException.java | 63 ++ .../fabric8/mockwebserver/ServerRequest.java | 19 + .../fabric8/mockwebserver/ServerResponse.java | 26 + .../fabric8/mockwebserver/crud/Attribute.java | 91 +++ .../crud/AttributeExtractor.java | 24 + .../mockwebserver/crud/AttributeSet.java | 149 ++++ .../mockwebserver/crud/AttributeType.java | 25 + .../mockwebserver/crud/CrudDispatcher.java | 230 ++++++ .../io/fabric8/mockwebserver/crud/Key.java | 47 ++ .../mockwebserver/crud/ResponseComposer.java | 23 + .../io/fabric8/mockwebserver/crud/Value.java | 60 ++ .../mockwebserver/dsl/DelayPathable.java | 21 + .../dsl/DelayTimesOrOnceable.java | 20 + .../fabric8/mockwebserver/dsl/Delayable.java | 26 + .../fabric8/mockwebserver/dsl/Doneable.java | 21 + .../fabric8/mockwebserver/dsl/Emitable.java | 29 + .../mockwebserver/dsl/EventDoneable.java | 20 + .../fabric8/mockwebserver/dsl/Eventable.java | 31 + .../fabric8/mockwebserver/dsl/Failable.java | 22 + .../fabric8/mockwebserver/dsl/Function.java | 21 + .../mockwebserver/dsl/HttpHeaderable.java | 25 + .../fabric8/mockwebserver/dsl/HttpMethod.java | 30 + .../mockwebserver/dsl/HttpMethodable.java | 33 + .../mockwebserver/dsl/HttpStatusable.java | 23 + .../dsl/MockServerExpectation.java | 24 + .../fabric8/mockwebserver/dsl/Onceable.java | 21 + .../fabric8/mockwebserver/dsl/Openable.java | 22 + .../fabric8/mockwebserver/dsl/Pathable.java | 23 + .../fabric8/mockwebserver/dsl/Replyable.java | 34 + .../dsl/ReturnOrWebsocketable.java | 20 + .../fabric8/mockwebserver/dsl/Returnable.java | 25 + .../mockwebserver/dsl/Schedulable.java | 25 + .../dsl/TimesOnceableOrHttpHeaderable.java | 20 + .../mockwebserver/dsl/TimesOrOnceable.java | 21 + .../dsl/TimesSchedulableOrOnceable.java | 22 + .../fabric8/mockwebserver/dsl/Timesable.java | 23 + .../dsl/WebSocketSessionBuilder.java | 22 + .../mockwebserver/dsl/WebSocketable.java | 32 + .../internal/ChunkedResponse.java | 85 +++ .../InlineWebSocketSessionBuilder.java | 213 ++++++ .../internal/MockDispatcher.java | 80 +++ .../internal/MockSSLContextFactory.java | 40 ++ .../internal/MockServerExpectationImpl.java | 357 ++++++++++ .../mockwebserver/internal/SimpleRequest.java | 71 ++ .../internal/SimpleResponse.java | 117 +++ .../internal/WebSocketMessage.java | 81 +++ .../internal/WebSocketSession.java | 192 +++++ .../mockwebserver/utils/BodyProvider.java | 27 + .../mockwebserver/utils/CertUtils.java | 113 +++ .../mockwebserver/utils/Closeables.java | 37 + .../mockwebserver/utils/PKCS1Util.java | 141 ++++ .../mockwebserver/utils/ResponseProvider.java | 32 + .../utils/ResponseProviders.java | 151 ++++ .../fabric8/mockwebserver/utils/SSLUtils.java | 72 ++ .../resources/ssl/fabric8-private-key.pem | 50 ++ .../src/main/resources/ssl/fabric8.crt | 31 + .../src/main/resources/ssl/fabric8.csr | 27 + .../src/main/resources/ssl/fabric8.pub | 1 + .../DefaultMockServerCrudTest.groovy | 142 ++++ .../DefaultMockServerHttpsTest.groovy | 45 ++ .../DefaultMockServerTest.groovy | 668 ++++++++++++++++++ .../DefaultMockServerWebSocketTest.groovy | 167 +++++ .../mockwebserver/JsonResponseComposer.groovy | 27 + .../MockServerExceptionTest.groovy | 65 ++ .../io/fabric8/mockwebserver/User.groovy | 86 +++ .../UserAttributeExtractor.groovy | 40 ++ .../crud/AttributeSetTest.groovy | 164 +++++ .../mockwebserver/crud/AttributeTest.groovy | 55 ++ .../crud/CrudDispatcherTest.groovy | 124 ++++ pom.xml | 44 +- 76 files changed, 5370 insertions(+), 18 deletions(-) create mode 100644 junit/mockwebserver/pom.xml create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/Context.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/DefaultMockServer.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/MockServer.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/MockServerException.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/ServerRequest.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/ServerResponse.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/Attribute.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/AttributeExtractor.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/AttributeSet.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/AttributeType.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/CrudDispatcher.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/Key.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/ResponseComposer.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/Value.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/DelayPathable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/DelayTimesOrOnceable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Delayable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Doneable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Emitable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/EventDoneable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Eventable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Failable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Function.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/HttpHeaderable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/HttpMethod.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/HttpMethodable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/HttpStatusable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/MockServerExpectation.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Onceable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Openable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Pathable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Replyable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/ReturnOrWebsocketable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Returnable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Schedulable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/TimesOnceableOrHttpHeaderable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/TimesOrOnceable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/TimesSchedulableOrOnceable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Timesable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/WebSocketSessionBuilder.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/WebSocketable.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/ChunkedResponse.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/InlineWebSocketSessionBuilder.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/MockDispatcher.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/MockSSLContextFactory.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/MockServerExpectationImpl.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/SimpleRequest.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/SimpleResponse.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/WebSocketMessage.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/WebSocketSession.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/BodyProvider.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/CertUtils.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/Closeables.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/PKCS1Util.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/ResponseProvider.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/ResponseProviders.java create mode 100644 junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/SSLUtils.java create mode 100644 junit/mockwebserver/src/main/resources/ssl/fabric8-private-key.pem create mode 100644 junit/mockwebserver/src/main/resources/ssl/fabric8.crt create mode 100644 junit/mockwebserver/src/main/resources/ssl/fabric8.csr create mode 100644 junit/mockwebserver/src/main/resources/ssl/fabric8.pub create mode 100644 junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/DefaultMockServerCrudTest.groovy create mode 100644 junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/DefaultMockServerHttpsTest.groovy create mode 100644 junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/DefaultMockServerTest.groovy create mode 100644 junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/DefaultMockServerWebSocketTest.groovy create mode 100644 junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/JsonResponseComposer.groovy create mode 100644 junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/MockServerExceptionTest.groovy create mode 100644 junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/User.groovy create mode 100644 junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/UserAttributeExtractor.groovy create mode 100644 junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/crud/AttributeSetTest.groovy create mode 100644 junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/crud/AttributeTest.groovy create mode 100644 junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/crud/CrudDispatcherTest.groovy diff --git a/CHANGELOG.md b/CHANGELOG.md index 60f80987a0a..7825642c2be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ * Fix #5373: Gradle base API based on v8.2.1 #### New Features +* Fix #5430: Mock Web Server migrated to this repository #### _**Note**_: Breaking changes * Fix #5343: Removed `io.fabric8.kubernetes.model.annotation.PrinterColumn`, use `io.fabric8.crd.generator.annotation.PrinterColumn` diff --git a/junit/kubernetes-server-mock/src/main/java/io/fabric8/kubernetes/client/server/mock/crud/KubernetesCrudDispatcherHandler.java b/junit/kubernetes-server-mock/src/main/java/io/fabric8/kubernetes/client/server/mock/crud/KubernetesCrudDispatcherHandler.java index bb1c41305b0..6cc79611aa1 100644 --- a/junit/kubernetes-server-mock/src/main/java/io/fabric8/kubernetes/client/server/mock/crud/KubernetesCrudDispatcherHandler.java +++ b/junit/kubernetes-server-mock/src/main/java/io/fabric8/kubernetes/client/server/mock/crud/KubernetesCrudDispatcherHandler.java @@ -52,7 +52,7 @@ default MockResponse handle(RecordedRequest request) throws KubernetesCrudDispat default void validatePath(AttributeSet query, JsonNode updatedResource) throws KubernetesCrudDispatcherException { // metadata.name final String updatedName = updatedResource.path(METADATA).path(NAME).asText(); - final String pathName = query.getAttribute(NAME).getValue().toString(); + final String pathName = query.getAttribute(NAME).getValues().iterator().next().toString(); if (!updatedName.isEmpty() && !pathName.equals(updatedName)) { throw new KubernetesCrudDispatcherException( "the name of the object (" + updatedName + ") does not match the name on the URL (" + pathName + ")", @@ -62,7 +62,7 @@ default void validatePath(AttributeSet query, JsonNode updatedResource) throws K // metadata.namespace if (query.getAttribute(NAMESPACE) != null) { final String updatedNamespace = updatedResource.path(METADATA).path(NAMESPACE).asText(); - final String pathNamespace = query.getAttribute(NAMESPACE).getValue().toString(); + final String pathNamespace = query.getAttribute(NAMESPACE).getValues().iterator().next().toString(); if (!updatedNamespace.isEmpty() && !updatedNamespace.equals(pathNamespace)) { throw new KubernetesCrudDispatcherException( "the namespace of the object (" + updatedNamespace + ") does not match the namespace on the URL (" diff --git a/junit/mockwebserver/pom.xml b/junit/mockwebserver/pom.xml new file mode 100644 index 00000000000..aeb1d584df8 --- /dev/null +++ b/junit/mockwebserver/pom.xml @@ -0,0 +1,72 @@ + + + + 4.0.0 + + io.fabric8 + kubernetes-client-project + 6.9-SNAPSHOT + ../../pom.xml + + + mockwebserver + jar + + Fabric8 :: Mock Web Server + + + + com.squareup.okhttp3 + mockwebserver + + + com.fasterxml.jackson.core + jackson-databind + + + io.fabric8 + zjsonpatch + + + org.spockframework + spock-core + test + + + + + + + org.codehaus.gmavenplus + gmavenplus-plugin + ${gmavenplus-plugin.version} + + + + addSources + addTestSources + compileTests + + + + + + + + diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/Context.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/Context.java new file mode 100644 index 00000000000..1357f13d590 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/Context.java @@ -0,0 +1,36 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class Context { + + private final ObjectMapper mapper; + + public Context() { + this(new ObjectMapper()); + } + + public Context(ObjectMapper mapper) { + this.mapper = mapper; + } + + public ObjectMapper getMapper() { + return mapper; + } + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/DefaultMockServer.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/DefaultMockServer.java new file mode 100644 index 00000000000..ff9c1ad7154 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/DefaultMockServer.java @@ -0,0 +1,210 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver; + +import io.fabric8.mockwebserver.dsl.MockServerExpectation; +import io.fabric8.mockwebserver.internal.MockDispatcher; +import io.fabric8.mockwebserver.internal.MockSSLContextFactory; +import io.fabric8.mockwebserver.internal.MockServerExpectationImpl; +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Proxy; +import java.util.HashMap; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +public class DefaultMockServer implements MockServer { + + private final Context context; + private final boolean useHttps; + private final MockWebServer server; + private final Map> responses; + private final AtomicInteger lastRequestCount; + private final AtomicReference lastRequest; + + private final AtomicBoolean initialized = new AtomicBoolean(); + private final AtomicBoolean shutdown = new AtomicBoolean(); + + public DefaultMockServer() { + this(new Context(), new MockWebServer(), new HashMap<>(), false); + } + + public DefaultMockServer(boolean useHttps) { + this(new Context(), new MockWebServer(), new HashMap<>(), useHttps); + } + + public DefaultMockServer(MockWebServer server, Map> responses, boolean useHttps) { + this(new Context(), server, responses, useHttps); + } + + public DefaultMockServer(Context context, MockWebServer server, Map> responses, + boolean useHttps) { + this(context, server, responses, new MockDispatcher(responses), useHttps); + } + + public DefaultMockServer(Context context, MockWebServer server, Map> responses, + Dispatcher dispatcher, boolean useHttps) { + this.context = context; + this.useHttps = useHttps; + this.server = server; + this.responses = responses; + this.lastRequest = new AtomicReference<>(); + this.lastRequestCount = new AtomicInteger(0); + this.server.setDispatcher(dispatcher); + } + + private void startInternal() { + if (initialized.compareAndSet(false, true)) { + if (useHttps) { + server.useHttps(MockSSLContextFactory.create().getSocketFactory(), false); + } + onStart(); + } + } + + private void shutdownInternal() { + if (shutdown.compareAndSet(false, true)) { + onShutdown(); + } + } + + public final void start() { + try { + startInternal(); + server.start(); + } catch (IOException e) { + throw new MockServerException("Exception when starting DefaultMockServer", e); + } + } + + public final void start(int port) { + try { + startInternal(); + server.start(port); + } catch (IOException e) { + throw new MockServerException("Exception when starting DefaultMockServer with port", e); + } + } + + public final void start(InetAddress inetAddress, int port) { + try { + startInternal(); + server.start(inetAddress, port); + } catch (IOException e) { + throw new MockServerException("Exception when starting DefaultMockServer with InetAddress and port", e); + } + } + + public final void shutdown() { + try { + server.shutdown(); + } catch (IOException e) { + throw new MockServerException("Exception when stopping DefaultMockServer", e); + } finally { + shutdownInternal(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public String url(String path) { + return server.url(path).toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public int getPort() { + return server.getPort(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getHostName() { + return server.getHostName(); + } + + /** + * {@inheritDoc} + */ + @Override + public Proxy toProxyAddress() { + return server.toProxyAddress(); + } + + /** + * {@inheritDoc} + */ + @Override + public MockServerExpectation expect() { + return new MockServerExpectationImpl(responses, context); + } + + /** + * {@inheritDoc} + */ + @Override + public int getRequestCount() { + return server.getRequestCount(); + } + + /** + * {@inheritDoc} + */ + @Override + public RecordedRequest takeRequest() throws InterruptedException { + return server.takeRequest(); + } + + /** + * {@inheritDoc} + */ + @Override + public RecordedRequest takeRequest(long timeout, TimeUnit unit) throws InterruptedException { + return server.takeRequest(timeout, unit); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized RecordedRequest getLastRequest() throws InterruptedException { + if (lastRequest.get() != null && getRequestCount() == lastRequestCount.get()) { + return lastRequest.get(); + } + int requestCount = getRequestCount() - lastRequestCount.getAndSet(getRequestCount()); + RecordedRequest latestRequest = null; + while (requestCount-- > 0) { + latestRequest = takeRequest(); + } + lastRequest.set(latestRequest); + return latestRequest; + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/MockServer.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/MockServer.java new file mode 100644 index 00000000000..ea2d0de4e32 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/MockServer.java @@ -0,0 +1,107 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver; + +import io.fabric8.mockwebserver.dsl.MockServerExpectation; +import okhttp3.mockwebserver.RecordedRequest; + +import java.net.Proxy; +import java.util.concurrent.TimeUnit; + +public interface MockServer { + + /** + * This method is called right before start. Override it to add extra initialization. + */ + default void onStart() { + } + + /** + * This method is called right after shutdown. Override it to add extra cleanup. + */ + default void onShutdown() { + } + + /** + * The port for the {@link okhttp3.mockwebserver.MockWebServer}. + * + * @return the MockWebServer port. + */ + int getPort(); + + /** + * The host name for the {@link okhttp3.mockwebserver.MockWebServer}. + * + * @return the MockWebServer host name; + */ + String getHostName(); + + /** + * Returns a {@link Proxy} for the {@link okhttp3.mockwebserver.MockWebServer} with the current HostName and Port. + * + * @return a Proxy for the MockWebServer. + */ + Proxy toProxyAddress(); + + /** + * Returns a String URL for connecting to this server. + * + * @param path the request path, such as "/". + */ + String url(String path); + + /** + * Returns a {@link MockServerExpectation} to set the expectations. + * + * @return the MockServerExpectation builder. + */ + MockServerExpectation expect(); + + /** + * Returns the number of HTTP requests received thus far by this server. This may exceed the + * number of HTTP connections when connection reuse is in practice. + */ + int getRequestCount(); + + /** + * Awaits the next HTTP request, removes it, and returns it. Callers should use this to verify the + * request was sent as intended. This method will block until the request is available, possibly + * forever. + * + * @return the head of the request queue + */ + RecordedRequest takeRequest() throws InterruptedException; + + /** + * Awaits the next HTTP request (waiting up to the specified wait time if necessary), removes it, + * and returns it. Callers should use this to verify the request was sent as intended within the + * given time. + * + * @param timeout how long to wait before giving up, in units of {@code unit} + * @param unit a {@code TimeUnit} determining how to interpret the {@code timeout} parameter + * @return the head of the request queue + */ + RecordedRequest takeRequest(long timeout, TimeUnit unit) throws InterruptedException; + + /** + * Returns the last (most recent) HTTP request processed by the {@link okhttp3.mockwebserver.MockWebServer}. + * + * n.b. This method clears the request queue. + * + * @return the most recent RecordedRequest or null if none was processed. + */ + RecordedRequest getLastRequest() throws InterruptedException; +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/MockServerException.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/MockServerException.java new file mode 100644 index 00000000000..3631068cf47 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/MockServerException.java @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver; + +public class MockServerException extends RuntimeException { + + private static final long serialVersionUID = 2158577731194403856L; + + public MockServerException(String message) { + super(message); + } + + public MockServerException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Wraps the provided {@link Throwable} in a MockServerException in case it's checked exception. + * + *

+ * For RuntimeException instances, the original exception is returned. + * + * @param cause Throwable to wrap. + * @return the original exception in case it's unchecked, or a MockServerException wrapping it. + */ + public static RuntimeException launderThrowable(Throwable cause) { + return launderThrowable("An error has occurred.", cause); + } + + /** + * Wraps the provided {@link Throwable} in a MockServerException in case it's checked exception. + * + *

+ * For RuntimeException instances, the original exception is returned. + * + * @param message Message to use for the exception. + * @param cause Throwable to wrap. + * @return the original exception in case it's unchecked, or a MockServerException wrapping it. + */ + public static RuntimeException launderThrowable(String message, Throwable cause) { + if (cause instanceof RuntimeException) { + return (RuntimeException) cause; + } else if (cause instanceof Error) { + throw (Error) cause; + } else if (cause instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + throw new MockServerException(message, cause); + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/ServerRequest.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/ServerRequest.java new file mode 100644 index 00000000000..ceb7b962c74 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/ServerRequest.java @@ -0,0 +1,19 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver; + +public interface ServerRequest { +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/ServerResponse.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/ServerResponse.java new file mode 100644 index 00000000000..2b5e9cb34d4 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/ServerResponse.java @@ -0,0 +1,26 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.RecordedRequest; + +public interface ServerResponse { + + boolean isRepeatable(); + + MockResponse toMockResponse(RecordedRequest recordedRequest); +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/Attribute.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/Attribute.java new file mode 100644 index 00000000000..abc5c63a800 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/Attribute.java @@ -0,0 +1,91 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.crud; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static io.fabric8.mockwebserver.crud.AttributeType.WITH; + +public class Attribute { + + private final Key key; + private final List values; + private final AttributeType type; + + public Attribute(Key key, List values, AttributeType type) { + this.key = key; + this.values = values; + this.type = type; + } + + public Attribute(Key key, Value value, AttributeType type) { + this(key, Collections.singletonList(value), type); + } + + public Attribute(String key, String value, AttributeType type) { + this(new Key(key), new Value(value), type); + } + + public Attribute(String key, List values, AttributeType type) { + this(new Key(key), values.stream().map(Value::new).collect(Collectors.toList()), type); + } + + public Attribute(Key key, Value value) { + this(key, value, WITH); + } + + public Attribute(String key, String value) { + this(new Key(key), new Value(value)); + } + + public Key getKey() { + return key; + } + + public List getValues() { + return values; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Attribute attribute = (Attribute) o; + return Objects.equals(key, attribute.key) && Objects.equals(values, attribute.values); + } + + @Override + public int hashCode() { + return Objects.hash(key, values); + } + + @Override + public String toString() { + return "{" + + "key:" + key + + ", values:" + values + + '}'; + } + + public AttributeType getType() { + return type; + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/AttributeExtractor.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/AttributeExtractor.java new file mode 100644 index 00000000000..d3e3650ac53 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/AttributeExtractor.java @@ -0,0 +1,24 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.crud; + +public interface AttributeExtractor { + + AttributeSet fromPath(String path); + + AttributeSet fromResource(String resource); + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/AttributeSet.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/AttributeSet.java new file mode 100644 index 00000000000..df68305f914 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/AttributeSet.java @@ -0,0 +1,149 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.crud; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class AttributeSet { + + // Package-private for testing + final Map attributes; + + public static AttributeSet merge(AttributeSet... attributeSets) { + Map all = new HashMap<>(); + if (attributeSets != null) { + for (AttributeSet f : attributeSets) { + if (f != null && f.attributes != null) { + all.putAll(f.attributes); + } + } + } + return new AttributeSet(all); + } + + public static AttributeSet map(Attribute... attributes) { + Map all = new HashMap<>(); + if (attributes != null) { + for (Attribute a : attributes) { + all.put(a.getKey(), a); + } + } + return new AttributeSet(all); + } + + public AttributeSet(Attribute... attributes) { + this(Arrays.asList(attributes)); + } + + public AttributeSet(Collection attributes) { + this(AttributeSet.map(attributes.toArray(new Attribute[0])).attributes); + } + + public AttributeSet(Map attributes) { + this.attributes = attributes; + } + + public AttributeSet add(Attribute... attr) { + Map all = new HashMap<>(attributes); + for (Attribute a : attr) { + all.put(a.getKey(), a); + } + return new AttributeSet(all); + } + + public boolean containsKey(String key) { + return containsKey(new Key(key)); + } + + public boolean containsKey(Key key) { + return attributes.containsKey(key); + } + + /** + * matches if attributes in db has (or doesn't if WITHOUT command) a set of candidate attributes + * Also supports EXISTS and NOT_EXISTS operations + * + * @param candidate - set of candidate attributes + * @return match + */ + public boolean matches(AttributeSet candidate) { + return candidate.attributes.values() + .stream() + .allMatch(this::satisfiesAttribute); + } + + private boolean satisfiesAttribute(Attribute c) { + switch (c.getType()) { + case EXISTS: + return attributes.containsKey(c.getKey()); + case NOT_EXISTS: + return !attributes.containsKey(c.getKey()); + case IN: { + if (attributes.containsKey(c.getKey())) { + if (attributes.get(c.getKey()).getValues().size() > 1) { + throw new IllegalArgumentException("Attribute " + c.getKey() + " has multiple values, can't use IN operation"); + } + return c.getValues().contains(attributes.get(c.getKey()).getValues().iterator().next()); + } + return false; + } + case NOT_IN: { + if (attributes.containsKey(c.getKey())) { + if (attributes.get(c.getKey()).getValues().size() > 1) { + throw new IllegalArgumentException("Attribute " + c.getKey() + " has multiple values, can't use NOT_IN operation"); + } + return !c.getValues().contains(attributes.get(c.getKey()).getValues().iterator().next()); + } + return true; + } + case WITHOUT: + return !attributes.containsValue(c); + case WITH: + default: + return attributes.containsValue(c); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + AttributeSet that = (AttributeSet) o; + return Objects.equals(attributes, that.attributes); + } + + @Override + public int hashCode() { + return Objects.hash(attributes); + } + + public Attribute getAttribute(String key) { + return attributes.get(new Key(key)); + } + + @Override + public String toString() { + return "{" + + "attributes: " + attributes + + '}'; + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/AttributeType.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/AttributeType.java new file mode 100644 index 00000000000..209a49c749b --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/AttributeType.java @@ -0,0 +1,25 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.crud; + +public enum AttributeType { + WITH, + WITHOUT, + EXISTS, + NOT_EXISTS, + IN, + NOT_IN +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/CrudDispatcher.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/CrudDispatcher.java new file mode 100644 index 00000000000..f1d059b6115 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/CrudDispatcher.java @@ -0,0 +1,230 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.crud; + +import com.fasterxml.jackson.databind.JsonNode; +import io.fabric8.mockwebserver.Context; +import io.fabric8.mockwebserver.MockServerException; +import io.fabric8.zjsonpatch.JsonPatch; +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.RecordedRequest; + +import java.net.HttpURLConnection; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class CrudDispatcher extends Dispatcher { + + private static final String POST = "POST"; + private static final String PUT = "PUT"; + private static final String PATCH = "PATCH"; + private static final String GET = "GET"; + private static final String DELETE = "DELETE"; + + protected final Map map = Collections.synchronizedMap(new LinkedHashMap<>()); + + protected final Context context; + protected final AttributeExtractor attributeExtractor; + protected final ResponseComposer responseComposer; + + public CrudDispatcher(Context context, AttributeExtractor attributeExtractor, ResponseComposer responseComposer) { + this.context = context; + this.attributeExtractor = attributeExtractor; + this.responseComposer = responseComposer; + } + + @Override + public MockResponse dispatch(RecordedRequest request) { + String path = request.getPath(); + switch (request.getMethod().toUpperCase()) { + case POST: + return handleCreate(request); + case PUT: + return handleUpdate(request); + case PATCH: + return handlePatch(request); + case GET: + return handleGet(path); + case DELETE: + return handleDelete(path); + default: + return null; + } + } + + public MockResponse handleCreate(RecordedRequest request) { + return handleCreate(request.getPath(), request.getBody().readUtf8()); + } + + /** + * Adds the specified object to the in-memory db. + * + * @param path for the request. + * @param body Request body as String (UTF-8). + * @return a MockResponse to be dispatched. + */ + public MockResponse handleCreate(String path, String body) { + MockResponse response = new MockResponse(); + AttributeSet features = AttributeSet.merge(attributeExtractor.fromPath(path), attributeExtractor.fromResource(body)); + synchronized (map) { + map.put(features, body); + } + response.setBody(body); + response.setResponseCode(202); + return response; + } + + public MockResponse handlePatch(RecordedRequest request) { + return handlePatch(request.getPath(), request.getBody().readUtf8()); + } + + /** + * Patches the specified object to the in-memory db. + * + * @param path for the request. + * @param body Request body as String (UTF-8). + * @return a MockResponse to be dispatched. + */ + public MockResponse handlePatch(String path, String body) { + MockResponse response = new MockResponse(); + String existingObjectBody = doGet(path); + if (existingObjectBody == null) { + response.setResponseCode(404); + } else { + try { + JsonNode patch = context.getMapper().readTree(body); + JsonNode source = context.getMapper().readTree(existingObjectBody); + JsonNode updated = JsonPatch.apply(patch, source); + String updatedAsString = context.getMapper().writeValueAsString(updated); + AttributeSet features = AttributeSet.merge(attributeExtractor.fromPath(path), + attributeExtractor.fromResource(updatedAsString)); + synchronized (map) { + map.put(features, updatedAsString); + } + response.setResponseCode(202); + response.setBody(updatedAsString); + } catch (Exception e) { + throw new MockServerException("Exception when handling CRUD patch", e); + } + + } + return response; + } + + public MockResponse handleUpdate(RecordedRequest request) { + return handleUpdate(request.getPath(), request.getBody().readUtf8()); + } + + /** + * Updates the specified object to the in-memory db. + * + * @param path for the request. + * @param body Request body as String (UTF-8). + * @return a MockResponse to be dispatched. + */ + public MockResponse handleUpdate(String path, String body) { + final String currentItem = doGet(path); + final MockResponse response = handleCreate(path, body); + if (currentItem == null) { + response.setResponseCode(HttpURLConnection.HTTP_CREATED); + } + return response; + } + + /** + * Performs a get for the corresponding object from the in-memory db. + * + * @param path for the request. + * @return a MockResponse to be dispatched. + */ + public MockResponse handleGet(String path) { + MockResponse response = new MockResponse(); + + String body = doGet(path); + if (body == null) { + response.setResponseCode(404); + } else { + response.setResponseCode(200); + response.setBody(body); + } + return response; + } + + /** + * Performs a delete for the corresponding object from the in-memory db. + * + * @param path for the request. + * @return a MockResponse to be dispatched. + */ + public MockResponse handleDelete(String path) { + MockResponse response = new MockResponse(); + List items = new ArrayList<>(); + AttributeSet query = attributeExtractor.fromPath(path); + + synchronized (map) { + for (Map.Entry entry : map.entrySet()) { + if (entry.getKey().matches(query)) { + items.add(entry.getKey()); + } + } + if (!items.isEmpty()) { + for (AttributeSet item : items) { + map.remove(item); + } + response.setResponseCode(200); + } else { + response.setResponseCode(404); + } + } + return response; + } + + public Map getMap() { + return map; + } + + public AttributeExtractor getAttributeExtractor() { + return attributeExtractor; + } + + public ResponseComposer getResponseComposer() { + return responseComposer; + } + + private String doGet(String path) { + List items = new ArrayList<>(); + AttributeSet query = attributeExtractor.fromPath(path); + synchronized (map) { + for (Map.Entry entry : map.entrySet()) { + if (entry.getKey().matches(query)) { + items.add(entry.getValue()); + } + } + } + + if (items.isEmpty()) { + return null; + } else if (items.size() == 1) { + return items.get(0); + } else { + return responseComposer.compose(items); + } + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/Key.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/Key.java new file mode 100644 index 00000000000..ca41a2803ea --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/Key.java @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.crud; + +import java.util.Objects; + +public class Key { + + private final String name; + + public Key(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Key key = (Key) o; + return Objects.equals(name, key.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + @Override + public String toString() { + return name; + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/ResponseComposer.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/ResponseComposer.java new file mode 100644 index 00000000000..14d1b8c5700 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/ResponseComposer.java @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.crud; + +import java.util.Collection; + +public interface ResponseComposer { + + String compose(Collection items); +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/Value.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/Value.java new file mode 100644 index 00000000000..047ac4b9d31 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/crud/Value.java @@ -0,0 +1,60 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.crud; + +public class Value { + + private static final String ANY = "*"; + + private final String val; + + public Value(String value) { + this.val = value; + } + + @Override + // TODO: There's a BUG here, equals({val: "*"} is true but might have different hashCode + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + if (ANY.equals(val)) { + return true; + } + + Value key = (Value) o; + + if (ANY.equals(key.val)) { + return true; + } + return val != null ? val.equals(key.val) : key.val == null; + } + + @Override + public int hashCode() { + return val != null ? val.hashCode() : 0; + } + + @Override + public String toString() { + return val; + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/DelayPathable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/DelayPathable.java new file mode 100644 index 00000000000..bf135262b1f --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/DelayPathable.java @@ -0,0 +1,21 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.dsl; + +public interface DelayPathable extends Delayable>, + Pathable { + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/DelayTimesOrOnceable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/DelayTimesOrOnceable.java new file mode 100644 index 00000000000..afe11633109 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/DelayTimesOrOnceable.java @@ -0,0 +1,20 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.dsl; + +public interface DelayTimesOrOnceable extends Delayable, TimesOrOnceable { + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Delayable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Delayable.java new file mode 100644 index 00000000000..6627982076d --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Delayable.java @@ -0,0 +1,26 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.dsl; + +import java.util.concurrent.TimeUnit; + +public interface Delayable { + + T delay(long delay, TimeUnit delayUnit); + + T delay(long delayInMilliseconds); + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Doneable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Doneable.java new file mode 100644 index 00000000000..88f9dcacc34 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Doneable.java @@ -0,0 +1,21 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.dsl; + +public interface Doneable { + T done(); +} \ No newline at end of file diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Emitable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Emitable.java new file mode 100644 index 00000000000..3ab2823018c --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Emitable.java @@ -0,0 +1,29 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.dsl; + +public interface Emitable { + + /** + * Emit an event. This will be received by the client's onMessage. + * + * @param event + * @return + */ + T andEmit(Object event); + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/EventDoneable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/EventDoneable.java new file mode 100644 index 00000000000..2d744379542 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/EventDoneable.java @@ -0,0 +1,20 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.dsl; + +public interface EventDoneable extends Eventable>, Doneable { +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Eventable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Eventable.java new file mode 100644 index 00000000000..ff6a1a2c647 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Eventable.java @@ -0,0 +1,31 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.dsl; + +public interface Eventable { + + Emitable> expect(Object in); + + Emitable> expectHttpRequest(final String path); + + Emitable> expectSentWebSocketMessage(final Object in); + + Emitable waitFor(long millis); + + Emitable immediately(); + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Failable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Failable.java new file mode 100644 index 00000000000..dcd3178e3df --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Failable.java @@ -0,0 +1,22 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.dsl; + +public interface Failable { + + T failure(Object response, Exception e); +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Function.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Function.java new file mode 100644 index 00000000000..990910a61de --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Function.java @@ -0,0 +1,21 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.dsl; + +public interface Function { + O apply(I input); +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/HttpHeaderable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/HttpHeaderable.java new file mode 100644 index 00000000000..f4c7c80636f --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/HttpHeaderable.java @@ -0,0 +1,25 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.dsl; + +public interface HttpHeaderable { + + T withHeader(String header); + + T withHeader(String name, String value); + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/HttpMethod.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/HttpMethod.java new file mode 100644 index 00000000000..817af73b793 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/HttpMethod.java @@ -0,0 +1,30 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.dsl; + +public enum HttpMethod { + + GET, + POST, + PUT, + PATCH, + DELETE, + OPTIONS, + CONNECT, + ANY + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/HttpMethodable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/HttpMethodable.java new file mode 100644 index 00000000000..7d48218bc24 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/HttpMethodable.java @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.dsl; + +public interface HttpMethodable { + + T any(); + + T post(); + + T get(); + + T put(); + + T delete(); + + T patch(); + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/HttpStatusable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/HttpStatusable.java new file mode 100644 index 00000000000..c39de4d17bf --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/HttpStatusable.java @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.dsl; + +public interface HttpStatusable { + + T withStatus(int statusCode); + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/MockServerExpectation.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/MockServerExpectation.java new file mode 100644 index 00000000000..c21c65be934 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/MockServerExpectation.java @@ -0,0 +1,24 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.dsl; + +public interface MockServerExpectation + extends HttpMethodable>>>, + DelayPathable>>, + ReturnOrWebsocketable>, + TimesOnceableOrHttpHeaderable { + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Onceable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Onceable.java new file mode 100644 index 00000000000..b586b042923 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Onceable.java @@ -0,0 +1,21 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.dsl; + +public interface Onceable { + + T once(); +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Openable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Openable.java new file mode 100644 index 00000000000..bb88bd9cf20 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Openable.java @@ -0,0 +1,22 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.dsl; + +public interface Openable { + + T open(Object... response); +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Pathable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Pathable.java new file mode 100644 index 00000000000..c26cb2ba974 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Pathable.java @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.dsl; + +public interface Pathable { + + T withPath(String path); + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Replyable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Replyable.java new file mode 100644 index 00000000000..4f7669f6350 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Replyable.java @@ -0,0 +1,34 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.dsl; + +import io.fabric8.mockwebserver.utils.BodyProvider; +import io.fabric8.mockwebserver.utils.ResponseProvider; + +import java.util.List; + +public interface Replyable { + + T andReply(int statusCode, BodyProvider contentSupplier); + + T andReply(ResponseProvider contentSupplier); + + T andReplyChunked(int statusCode, BodyProvider> content); + + T andReplyChunked(ResponseProvider> content); + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/ReturnOrWebsocketable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/ReturnOrWebsocketable.java new file mode 100644 index 00000000000..d1aaa1de3dd --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/ReturnOrWebsocketable.java @@ -0,0 +1,20 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.dsl; + +public interface ReturnOrWebsocketable extends Returnable, WebSocketable>, Replyable { +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Returnable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Returnable.java new file mode 100644 index 00000000000..3a7605d93a3 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Returnable.java @@ -0,0 +1,25 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.dsl; + +public interface Returnable { + + T andReturn(int statusCode, Object content); + + T andReturnChunked(int statusCode, Object... content); + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Schedulable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Schedulable.java new file mode 100644 index 00000000000..e3f1ae285c7 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Schedulable.java @@ -0,0 +1,25 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.dsl; + +import java.util.concurrent.TimeUnit; + +public interface Schedulable { + + T every(long initialDelay, long period, TimeUnit timeUnit); + + T every(long period, TimeUnit timeUnit); +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/TimesOnceableOrHttpHeaderable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/TimesOnceableOrHttpHeaderable.java new file mode 100644 index 00000000000..a60f4b95931 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/TimesOnceableOrHttpHeaderable.java @@ -0,0 +1,20 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.dsl; + +public interface TimesOnceableOrHttpHeaderable extends HttpHeaderable>, TimesOrOnceable { + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/TimesOrOnceable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/TimesOrOnceable.java new file mode 100644 index 00000000000..5e91362ee4b --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/TimesOrOnceable.java @@ -0,0 +1,21 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.dsl; + +public interface TimesOrOnceable extends Timesable, + Onceable { + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/TimesSchedulableOrOnceable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/TimesSchedulableOrOnceable.java new file mode 100644 index 00000000000..e53348d026f --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/TimesSchedulableOrOnceable.java @@ -0,0 +1,22 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.dsl; + +public interface TimesSchedulableOrOnceable extends TimesOrOnceable, + Schedulable> { + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Timesable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Timesable.java new file mode 100644 index 00000000000..6027c6ad7c3 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/Timesable.java @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.dsl; + +public interface Timesable { + + T always(); + + T times(int times); +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/WebSocketSessionBuilder.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/WebSocketSessionBuilder.java new file mode 100644 index 00000000000..20d37852fbe --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/WebSocketSessionBuilder.java @@ -0,0 +1,22 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.dsl; + +public interface WebSocketSessionBuilder extends + Openable>, + Failable { +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/WebSocketable.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/WebSocketable.java new file mode 100644 index 00000000000..22f1f1aab41 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/dsl/WebSocketable.java @@ -0,0 +1,32 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.dsl; + +import java.util.concurrent.ScheduledExecutorService; + +public interface WebSocketable { + + T andUpgradeToWebSocket(); + + /** + * @deprecated the provided ScheduledExecutorService is not used, use {@link #andUpgradeToWebSocket()} instead. + * The ExecutorService is handled internally by WebSocketSession, external executors are no longer allowed. + */ + @Deprecated + T andUpgradeToWebSocket(ScheduledExecutorService executor); + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/ChunkedResponse.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/ChunkedResponse.java new file mode 100644 index 00000000000..d5d6df848b8 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/ChunkedResponse.java @@ -0,0 +1,85 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.internal; + +import io.fabric8.mockwebserver.ServerResponse; +import io.fabric8.mockwebserver.utils.ResponseProvider; +import io.fabric8.mockwebserver.utils.ResponseProviders; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.RecordedRequest; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class ChunkedResponse implements ServerResponse { + + private static final int DEFAULT_MAX_CHUNK_SIZE = 204800; + private final ResponseProvider> bodyProvider; + private final boolean repeatable; + private final long responseDelay; + private final TimeUnit responseDelayUnit; + + public ChunkedResponse(boolean repeatable, int statusCode, String... body) { + this(repeatable, ResponseProviders.ofAll(statusCode, body)); + } + + public ChunkedResponse(boolean repeatable, ResponseProvider> bodyProvider) { + this(repeatable, 0, TimeUnit.MILLISECONDS, bodyProvider); + } + + public ChunkedResponse(boolean repeatable, int statusCode, long responseDelay, TimeUnit responseDelayUnit, String... body) { + this(repeatable, responseDelay, responseDelayUnit, ResponseProviders.ofAll(statusCode, body)); + } + + public ChunkedResponse(boolean repeatable, long responseDelay, TimeUnit responseDelayUnit, + ResponseProvider> bodyProvider) { + this.bodyProvider = bodyProvider; + this.repeatable = repeatable; + this.responseDelay = responseDelay; + this.responseDelayUnit = responseDelayUnit; + } + + public ResponseProvider> getBodyProvider() { + return bodyProvider; + } + + @Override + public MockResponse toMockResponse(RecordedRequest request) { + MockResponse mockResponse = new MockResponse(); + mockResponse.setHeaders(bodyProvider.getHeaders()); + mockResponse.setChunkedBody(concatBody(request), DEFAULT_MAX_CHUNK_SIZE); + mockResponse.setResponseCode(bodyProvider.getStatusCode(request)); + + if (responseDelay > 0) { + mockResponse.setBodyDelay(responseDelay, responseDelayUnit); + } + + return mockResponse; + } + + private String concatBody(RecordedRequest request) { + StringBuilder sb = new StringBuilder(); + for (String s : bodyProvider.getBody(request)) { + sb.append(s); + } + return sb.toString(); + } + + @Override + public boolean isRepeatable() { + return repeatable; + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/InlineWebSocketSessionBuilder.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/InlineWebSocketSessionBuilder.java new file mode 100644 index 00000000000..6c72e92e477 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/InlineWebSocketSessionBuilder.java @@ -0,0 +1,213 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.internal; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.fabric8.mockwebserver.Context; +import io.fabric8.mockwebserver.MockServerException; +import io.fabric8.mockwebserver.dsl.Emitable; +import io.fabric8.mockwebserver.dsl.EventDoneable; +import io.fabric8.mockwebserver.dsl.Function; +import io.fabric8.mockwebserver.dsl.TimesOrOnceable; +import io.fabric8.mockwebserver.dsl.WebSocketSessionBuilder; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Queue; + +public class InlineWebSocketSessionBuilder implements WebSocketSessionBuilder, EventDoneable { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private final Context context; + private final Function function; + private WebSocketSession session; + + public InlineWebSocketSessionBuilder(Context context, Function function) { + this.context = context; + this.function = function; + } + + @Override + public EventDoneable open(Object... response) { + this.session = new WebSocketSession(toWebSocketMessages(response), null, null); + return this; + } + + @Override + public T failure(Object response, Exception e) { + return function.apply(new WebSocketSession(Collections.emptyList(), toWebSocketMessage(response), e)); + } + + @Override + public T done() { + return function.apply(session); + } + + @Override + public Emitable>> expect(final Object in) { + return event -> new TimesOrOnceable>() { + @Override + public EventDoneable always() { + enqueue(in, toWebSocketMessage(event, false)); + return InlineWebSocketSessionBuilder.this; + } + + @Override + public EventDoneable once() { + enqueue(in, toWebSocketMessage(event, true)); + return InlineWebSocketSessionBuilder.this; + } + + @Override + public EventDoneable times(int times) { + for (int i = 0; i < times; i++) { + enqueue(in, toWebSocketMessage(event, true)); + } + return InlineWebSocketSessionBuilder.this; + } + }; + } + + @Override + public Emitable>> expectHttpRequest(final String path) { + return event -> new TimesOrOnceable>() { + @Override + public EventDoneable always() { + enqueueSimpleRequest(new SimpleRequest(path), toWebSocketMessage(event, false)); + return InlineWebSocketSessionBuilder.this; + } + + @Override + public EventDoneable once() { + enqueueSimpleRequest(new SimpleRequest(path), toWebSocketMessage(event, true)); + return InlineWebSocketSessionBuilder.this; + } + + @Override + public EventDoneable times(int times) { + for (int i = 0; i < times; i++) { + enqueueSimpleRequest(new SimpleRequest(path), toWebSocketMessage(event, true)); + } + return InlineWebSocketSessionBuilder.this; + } + }; + } + + @Override + public Emitable>> expectSentWebSocketMessage(final Object in) { + return event -> new TimesOrOnceable>() { + @Override + public EventDoneable always() { + enqueueForSentWebSocketMessage(in, toWebSocketMessage(event, false)); + return InlineWebSocketSessionBuilder.this; + } + + @Override + public EventDoneable once() { + enqueueForSentWebSocketMessage(in, toWebSocketMessage(event, true)); + return InlineWebSocketSessionBuilder.this; + } + + @Override + public EventDoneable times(int times) { + for (int i = 0; i < times; i++) { + enqueueForSentWebSocketMessage(in, toWebSocketMessage(event, true)); + } + return InlineWebSocketSessionBuilder.this; + } + }; + } + + @Override + public Emitable> waitFor(final long millis) { + return event -> { + session.getTimedEvents().add(toWebSocketMessage(millis, event)); + return InlineWebSocketSessionBuilder.this; + }; + } + + @Override + public Emitable> immediately() { + return waitFor(0); + } + + private List toWebSocketMessages(Object... messages) { + List response = new ArrayList<>(); + for (Object msg : messages) { + response.add(toWebSocketMessage(msg)); + } + return response; + } + + private WebSocketMessage toWebSocketMessage(Object content) { + return toWebSocketMessage(0L, content, true); + } + + private WebSocketMessage toWebSocketMessage(Long delay, Object content) { + return toWebSocketMessage(delay, content, true); + } + + private WebSocketMessage toWebSocketMessage(Object content, Boolean toBeRemoved) { + return toWebSocketMessage(0L, content, toBeRemoved); + } + + private WebSocketMessage toWebSocketMessage(Long delay, Object content, Boolean toBeRemoved) { + if (content instanceof String) { + return new WebSocketMessage(delay, (String) content, toBeRemoved); + } else if (content instanceof WebSocketMessage) { + return (WebSocketMessage) content; + } else { + try { + return toWebSocketMessage(delay, MAPPER.writeValueAsString(content), toBeRemoved); + } catch (JsonProcessingException e) { + throw new MockServerException("Exception when mapping to WebSocketMessage", e); + } + } + } + + private void enqueue(Object req, WebSocketMessage resp) { + Queue queuedResponses = session.getRequestEvents().get(req); + if (queuedResponses == null) { + queuedResponses = new ArrayDeque<>(); + session.getRequestEvents().put(req, queuedResponses); + } + queuedResponses.add(resp); + } + + private void enqueueForSentWebSocketMessage(Object req, WebSocketMessage resp) { + Queue queuedResponses = session.getSentWebSocketMessagesRequestEvents().get(req); + if (queuedResponses == null) { + queuedResponses = new ArrayDeque<>(); + session.getSentWebSocketMessagesRequestEvents().put(req, queuedResponses); + } + queuedResponses.add(resp); + } + + private void enqueueSimpleRequest(SimpleRequest req, WebSocketMessage resp) { + Queue queuedResponses = session.getHttpRequestEvents().get(req); + if (queuedResponses == null) { + queuedResponses = new ArrayDeque<>(); + session.getHttpRequestEvents().put(req, queuedResponses); + } + queuedResponses.add(resp); + } + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/MockDispatcher.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/MockDispatcher.java new file mode 100644 index 00000000000..8aa6d41bb64 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/MockDispatcher.java @@ -0,0 +1,80 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.internal; + +import io.fabric8.mockwebserver.ServerRequest; +import io.fabric8.mockwebserver.ServerResponse; +import io.fabric8.mockwebserver.dsl.HttpMethod; +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.RecordedRequest; + +import java.util.Collection; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class MockDispatcher extends Dispatcher { + + private final Map> responses; + private final Collection webSocketSessions = new ConcurrentLinkedQueue<>(); + + public MockDispatcher(Map> responses) { + this.responses = responses; + } + + @Override + public MockResponse dispatch(RecordedRequest request) { + for (WebSocketSession webSocketSession : webSocketSessions) { + webSocketSession.dispatch(request); + } + + HttpMethod method = HttpMethod.valueOf(request.getMethod()); + String path = request.getPath(); + SimpleRequest key = new SimpleRequest(method, path); + SimpleRequest keyForAnyMethod = new SimpleRequest(path); + if (responses.containsKey(key)) { + Queue queue = responses.get(key); + return handleResponse(queue.peek(), queue, request); + } else if (responses.containsKey(keyForAnyMethod)) { + Queue queue = responses.get(keyForAnyMethod); + return handleResponse(queue.peek(), queue, request); + } + return new MockResponse().setResponseCode(404); + } + + private MockResponse handleResponse(ServerResponse response, Queue queue, RecordedRequest request) { + if (response == null) { + return new MockResponse().setResponseCode(404); + } else if (!response.isRepeatable()) { + queue.remove(); + } + if (response instanceof SimpleResponse) { + SimpleResponse simpleResponse = (SimpleResponse) response; + if (simpleResponse.getWebSocketSession() != null) { + webSocketSessions.add(simpleResponse.getWebSocketSession()); + } + } + return response.toMockResponse(request); + } + + @Override + public void shutdown() { + webSocketSessions.forEach(WebSocketSession::shutdown); + super.shutdown(); + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/MockSSLContextFactory.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/MockSSLContextFactory.java new file mode 100644 index 00000000000..3a3a1279ffc --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/MockSSLContextFactory.java @@ -0,0 +1,40 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.internal; + +import io.fabric8.mockwebserver.MockServerException; +import io.fabric8.mockwebserver.utils.SSLUtils; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; + +public class MockSSLContextFactory { + + private MockSSLContextFactory() { + } + + public static SSLContext create() { + try { + KeyManager[] keyManagers = SSLUtils.keyManagers(MockSSLContextFactory.class.getResourceAsStream("/ssl/fabric8.crt"), + MockSSLContextFactory.class.getResourceAsStream("/ssl/fabric8-private-key.pem"), + "RSA", ""); + return SSLUtils.sslContext(keyManagers, null, true); + } catch (Exception e) { + throw new MockServerException("Exception creating SSLContext", e); + } + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/MockServerExpectationImpl.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/MockServerExpectationImpl.java new file mode 100644 index 00000000000..9ce3490853b --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/MockServerExpectationImpl.java @@ -0,0 +1,357 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.internal; + +import com.fasterxml.jackson.core.JsonProcessingException; +import io.fabric8.mockwebserver.Context; +import io.fabric8.mockwebserver.MockServerException; +import io.fabric8.mockwebserver.ServerRequest; +import io.fabric8.mockwebserver.ServerResponse; +import io.fabric8.mockwebserver.dsl.DelayPathable; +import io.fabric8.mockwebserver.dsl.Function; +import io.fabric8.mockwebserver.dsl.HttpMethod; +import io.fabric8.mockwebserver.dsl.MockServerExpectation; +import io.fabric8.mockwebserver.dsl.Pathable; +import io.fabric8.mockwebserver.dsl.ReturnOrWebsocketable; +import io.fabric8.mockwebserver.dsl.TimesOnceableOrHttpHeaderable; +import io.fabric8.mockwebserver.dsl.WebSocketSessionBuilder; +import io.fabric8.mockwebserver.utils.BodyProvider; +import io.fabric8.mockwebserver.utils.ResponseProvider; +import io.fabric8.mockwebserver.utils.ResponseProviders; +import okhttp3.Headers; +import okhttp3.mockwebserver.RecordedRequest; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class MockServerExpectationImpl implements MockServerExpectation { + + private final Context context; + private final HttpMethod method; + private final String path; + private final ResponseProvider bodyProvider; + private final ResponseProvider> chunksProvider; + private final long delay; + private final TimeUnit delayUnit; + private final int times; + + private final Map> responses; + + public MockServerExpectationImpl(Map> responses, Context context) { + this(context, HttpMethod.ANY, null, 200, null, null, 0, TimeUnit.SECONDS, 1, responses); + } + + public MockServerExpectationImpl(Context context, HttpMethod method, String path, int statusCode, String body, + String[] chunks, long delay, TimeUnit delayUnit, int times, Map> responses) { + this(context, method, path, ResponseProviders.of(statusCode, body), ResponseProviders.ofAll(statusCode, chunks), delay, + delayUnit, times, responses); + } + + public MockServerExpectationImpl(Context context, HttpMethod method, String path, ResponseProvider bodyProvider, + ResponseProvider> chunksProvider, long delay, TimeUnit delayUnit, int times, + Map> responses) { + this.context = context; + this.method = method; + this.path = path; + this.bodyProvider = bodyProvider; + this.chunksProvider = chunksProvider; + this.delay = delay; + this.delayUnit = delayUnit; + this.times = times; + this.responses = responses; + } + + @Override + public DelayPathable>> any() { + return new MockServerExpectationImpl(context, HttpMethod.ANY, path, bodyProvider, chunksProvider, delay, delayUnit, times, + responses); + } + + @Override + public DelayPathable>> post() { + return new MockServerExpectationImpl(context, HttpMethod.POST, path, bodyProvider, chunksProvider, delay, delayUnit, times, + responses); + } + + @Override + public DelayPathable>> get() { + return new MockServerExpectationImpl(context, HttpMethod.GET, path, bodyProvider, chunksProvider, delay, delayUnit, times, + responses); + } + + @Override + public DelayPathable>> put() { + return new MockServerExpectationImpl(context, HttpMethod.PUT, path, bodyProvider, chunksProvider, delay, delayUnit, times, + responses); + } + + @Override + public DelayPathable>> delete() { + return new MockServerExpectationImpl(context, HttpMethod.DELETE, path, bodyProvider, chunksProvider, delay, delayUnit, + times, responses); + } + + @Override + public DelayPathable>> patch() { + return new MockServerExpectationImpl(context, HttpMethod.PATCH, path, bodyProvider, chunksProvider, delay, delayUnit, times, + responses); + } + + @Override + public ReturnOrWebsocketable> withPath(String path) { + return new MockServerExpectationImpl(context, method, path, bodyProvider, chunksProvider, delay, delayUnit, times, + responses); + } + + @Override + public TimesOnceableOrHttpHeaderable andReturn(int statusCode, Object content) { + return new MockServerExpectationImpl(context, method, path, ResponseProviders.of(statusCode, toString(content)), + chunksProvider, delay, delayUnit, times, responses); + } + + @Override + public TimesOnceableOrHttpHeaderable andReply(int statusCode, BodyProvider content) { + return andReply(ResponseProviders.of(statusCode, content)); + } + + @Override + public TimesOnceableOrHttpHeaderable andReply(ResponseProvider content) { + return new MockServerExpectationImpl(context, method, path, toString(content), chunksProvider, delay, delayUnit, times, + responses); + } + + @Override + public TimesOnceableOrHttpHeaderable andReturnChunked(int statusCode, Object... contents) { + return new MockServerExpectationImpl(context, method, path, bodyProvider, + ResponseProviders.of(statusCode, toString(contents)), delay, delayUnit, times, responses); + } + + @Override + public TimesOnceableOrHttpHeaderable andReplyChunked(int statusCode, BodyProvider> contents) { + return andReplyChunked(ResponseProviders.of(statusCode, contents)); + } + + @Override + public TimesOnceableOrHttpHeaderable andReplyChunked(ResponseProvider> contents) { + return new MockServerExpectationImpl(context, method, path, bodyProvider, listToString(contents), delay, delayUnit, times, + responses); + } + + @Override + public Void always() { + enqueue(new SimpleRequest(method, path), createResponse(true, delay, delayUnit)); + return null;//Void + } + + @Override + public Void once() { + enqueue(new SimpleRequest(method, path), createResponse(false, delay, delayUnit)); + return null;//Void + } + + @Override + public Void times(int times) { + for (int i = 0; i < times; i++) { + once(); + } + return null;//Void + } + + @Override + public Pathable>> delay(long delay, TimeUnit delayUnit) { + return new MockServerExpectationImpl(context, method, path, bodyProvider, chunksProvider, delay, delayUnit, times, + responses); + } + + @Override + public Pathable>> delay(long delayInMilliseconds) { + return new MockServerExpectationImpl(context, method, path, bodyProvider, chunksProvider, delayInMilliseconds, + TimeUnit.MILLISECONDS, times, responses); + } + + @Override + public WebSocketSessionBuilder> andUpgradeToWebSocket() { + return new InlineWebSocketSessionBuilder<>(context, new WebSocketSessionConverter(this)); + } + + /** + * {@inheritDoc} + */ + @Override + public WebSocketSessionBuilder> andUpgradeToWebSocket(ScheduledExecutorService executor) { + return new InlineWebSocketSessionBuilder<>(context, new WebSocketSessionConverter(this)); + } + + @Override + public TimesOnceableOrHttpHeaderable withHeader(String header) { + bodyProvider.setHeaders(bodyProvider.getHeaders().newBuilder().add(header).build()); + return new MockServerExpectationImpl(context, method, path, bodyProvider, chunksProvider, delay, TimeUnit.MILLISECONDS, + times, responses); + } + + @Override + public TimesOnceableOrHttpHeaderable withHeader(String name, String value) { + bodyProvider.setHeaders(bodyProvider.getHeaders().newBuilder().add(name, value).build()); + return new MockServerExpectationImpl(context, method, path, bodyProvider, chunksProvider, delay, TimeUnit.MILLISECONDS, + times, responses); + } + + private void enqueue(ServerRequest req, ServerResponse resp) { + responses.computeIfAbsent(req, k -> new ArrayDeque<>()).add(resp); + } + + private ServerResponse createResponse(boolean repeatable, long delay, TimeUnit delayUnit) { + if (chunksProvider != null) { + return new ChunkedResponse(repeatable, delay, delayUnit, chunksProvider); + } else { + return new SimpleResponse(repeatable, bodyProvider, null, delay, delayUnit); + } + } + + private ResponseProvider toString(final ResponseProvider provider) { + return new ResponseProvider() { + @Override + public String getBody(RecordedRequest request) { + Object object = provider.getBody(request); + return MockServerExpectationImpl.this.toString(object); + } + + @Override + public int getStatusCode(RecordedRequest request) { + return provider.getStatusCode(request); + } + + @Override + public Headers getHeaders() { + return provider.getHeaders(); + } + + @Override + public void setHeaders(Headers headers) { + provider.setHeaders(headers); + } + }; + } + + private ResponseProvider> listToString(final ResponseProvider> provider) { + return new ResponseProvider>() { + @Override + public List getBody(RecordedRequest request) { + List objects = provider.getBody(request); + List strings = new ArrayList<>(objects.size()); + for (Object o : objects) { + strings.add(MockServerExpectationImpl.this.toString(o)); + } + return strings; + } + + @Override + public int getStatusCode(RecordedRequest request) { + return provider.getStatusCode(request); + } + + @Override + public Headers getHeaders() { + return provider.getHeaders(); + } + + @Override + public void setHeaders(Headers headers) { + provider.setHeaders(headers); + } + }; + } + + private String toString(Object object) { + if (object instanceof String) { + return (String) object; + } else { + try { + return context.getMapper().writeValueAsString(object); + } catch (JsonProcessingException e) { + throw new MockServerException("Exception when mapping Object to String", e); + } + } + } + + private List toString(Object[] object) { + return Stream.of(object) + .map(this::toString) + .collect(Collectors.toList()); + } + + private static final class WebSocketSessionConverter + implements Function> { + + private final MockServerExpectationImpl mse; + + public WebSocketSessionConverter(MockServerExpectationImpl mse) { + this.mse = mse; + } + + @Override + public TimesOnceableOrHttpHeaderable apply(final WebSocketSession webSocketSession) { + final Map headers = new HashMap<>(); + headers.put("Upgrade", "websocket"); + headers.put("Connection", "Upgrade"); + + return new TimesOnceableOrHttpHeaderable() { + @Override + public Void always() { + mse.enqueue(new SimpleRequest(mse.method, mse.path), + new SimpleResponse(true, ResponseProviders.of(101, "", headers), webSocketSession)); + return null;//Void + } + + @Override + public Void once() { + mse.enqueue(new SimpleRequest(mse.method, mse.path), + new SimpleResponse(false, ResponseProviders.of(101, "", headers), webSocketSession)); + return null;//Void + } + + @Override + public Void times(int times) { + for (int i = 0; i < times; i++) { + once(); + } + return null;//Void + } + + @Override + public TimesOnceableOrHttpHeaderable withHeader(String header) { + headers.put(header, ""); + return this;//Void + } + + @Override + public TimesOnceableOrHttpHeaderable withHeader(String name, String value) { + headers.put(name, value); + return this;//Void + } + }; + } + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/SimpleRequest.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/SimpleRequest.java new file mode 100644 index 00000000000..cd3bf1adbc7 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/SimpleRequest.java @@ -0,0 +1,71 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.internal; + +import io.fabric8.mockwebserver.ServerRequest; +import io.fabric8.mockwebserver.dsl.HttpMethod; + +public class SimpleRequest implements ServerRequest { + + private static final String ROOT = "/"; + + private final HttpMethod method; + private final String path; + + public SimpleRequest() { + this(HttpMethod.ANY, ROOT); + } + + public SimpleRequest(String path) { + this(HttpMethod.ANY, path); + } + + public SimpleRequest(HttpMethod method, String path) { + this.method = method; + this.path = path; + } + + public HttpMethod getMethod() { + return method; + } + + public String getPath() { + return path; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + SimpleRequest that = (SimpleRequest) o; + + if (method != that.method) + return false; + return path != null ? path.equals(that.path) : that.path == null; + + } + + @Override + public int hashCode() { + int result = method != null ? method.hashCode() : 0; + result = 31 * result + (path != null ? path.hashCode() : 0); + return result; + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/SimpleResponse.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/SimpleResponse.java new file mode 100644 index 00000000000..9515d398a53 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/SimpleResponse.java @@ -0,0 +1,117 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.internal; + +import io.fabric8.mockwebserver.ServerResponse; +import io.fabric8.mockwebserver.utils.ResponseProvider; +import io.fabric8.mockwebserver.utils.ResponseProviders; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.RecordedRequest; + +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +public class SimpleResponse implements ServerResponse { + + private static final String HTTP_HEADER_SEC_WEBSOCKET_PROTOCOL = "sec-websocket-protocol"; + + private final ResponseProvider bodyProvider; + + private final WebSocketSession webSocketSession; + private final boolean repeatable; + private final long responseDelay; + private final TimeUnit responseDelayUnit; + + public SimpleResponse(boolean repeatable, int statusCode, String body, WebSocketSession webSocketSession) { + this(repeatable, ResponseProviders.of(statusCode, body), webSocketSession); + } + + public SimpleResponse(boolean repeatable, ResponseProvider bodyProvider, WebSocketSession webSocketSession) { + this(repeatable, bodyProvider, webSocketSession, 0, TimeUnit.MILLISECONDS); + } + + public SimpleResponse(boolean repeatable, int statusCode, String body, WebSocketSession webSocketSession, long responseDelay, + TimeUnit responseDelayUnit) { + this(repeatable, ResponseProviders.of(statusCode, body), webSocketSession, responseDelay, responseDelayUnit); + } + + public SimpleResponse(boolean repeatable, ResponseProvider bodyProvider, WebSocketSession webSocketSession, + long responseDelay, TimeUnit responseDelayUnit) { + this.bodyProvider = bodyProvider; + this.webSocketSession = webSocketSession; + this.repeatable = repeatable; + this.responseDelay = responseDelay; + this.responseDelayUnit = responseDelayUnit; + } + + public ResponseProvider getBodyProvider() { + return bodyProvider; + } + + @Override + public MockResponse toMockResponse(RecordedRequest request) { + MockResponse mockResponse = new MockResponse(); + mockResponse.setHeaders(bodyProvider.getHeaders()); + mockResponse.setResponseCode(bodyProvider.getStatusCode(request)); + + if (webSocketSession != null) { + mockResponse.withWebSocketUpgrade(webSocketSession); + // see https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism + // see https://github.com/netty/netty/blob/4.1/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java#L366 + String requestWebsocketProtocol = request.getHeaders().get(HTTP_HEADER_SEC_WEBSOCKET_PROTOCOL); + if (requestWebsocketProtocol != null + // only add the response header if it's not set, to prevent changing custom response headers + && mockResponse.getHeaders().get(HTTP_HEADER_SEC_WEBSOCKET_PROTOCOL) == null) { + mockResponse.addHeader(HTTP_HEADER_SEC_WEBSOCKET_PROTOCOL, requestWebsocketProtocol); + } + } else { + mockResponse.setBody(bodyProvider.getBody(request)); + } + + if (responseDelay > 0) { + mockResponse.setBodyDelay(responseDelay, responseDelayUnit); + } + + return mockResponse; + } + + public WebSocketSession getWebSocketSession() { + return webSocketSession; + } + + @Override + public boolean isRepeatable() { + return repeatable; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + SimpleResponse that = (SimpleResponse) o; + return repeatable == that.repeatable && responseDelay == that.responseDelay + && Objects.equals(bodyProvider, that.bodyProvider) && Objects.equals(webSocketSession, that.webSocketSession) + && responseDelayUnit == that.responseDelayUnit; + } + + @Override + public int hashCode() { + return Objects.hash(bodyProvider, webSocketSession, repeatable, responseDelay, responseDelayUnit); + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/WebSocketMessage.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/WebSocketMessage.java new file mode 100644 index 00000000000..2d77852b5cb --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/WebSocketMessage.java @@ -0,0 +1,81 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.internal; + +import java.nio.charset.StandardCharsets; + +public class WebSocketMessage { + + private final Long delay; + private final byte[] body; + private final boolean toBeRemoved; + private final boolean binary; + + public WebSocketMessage(String body) { + this(0L, body, true); + } + + public WebSocketMessage(byte[] body) { + this(0L, body, true, true); + } + + public WebSocketMessage(String body, boolean toBeRemoved) { + this(0L, body.getBytes(StandardCharsets.UTF_8), toBeRemoved, false); + } + + public WebSocketMessage(byte[] body, boolean toBeRemoved) { + this(0L, body, toBeRemoved, true); + } + + public WebSocketMessage(Long delay, String body, boolean toBeRemoved) { + this(delay, body.getBytes(StandardCharsets.UTF_8), toBeRemoved, false); + } + + public WebSocketMessage(Long delay, byte[] body, boolean toBeRemoved) { + this(delay, body, toBeRemoved, true); + } + + public WebSocketMessage(Long delay, String body, boolean toBeRemoved, boolean binary) { + this(delay, body.getBytes(StandardCharsets.UTF_8), toBeRemoved, binary); + } + + public WebSocketMessage(Long delay, byte[] body, boolean toBeRemoved, boolean binary) { + this.delay = delay; + this.body = body; + this.toBeRemoved = toBeRemoved; + this.binary = binary; + } + + public Long getDelay() { + return delay; + } + + public String getBody() { + return new String(body); + } + + public boolean isToBeRemoved() { + return toBeRemoved; + } + + public byte[] getBytes() { + return body; + } + + public boolean isBinary() { + return binary; + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/WebSocketSession.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/WebSocketSession.java new file mode 100644 index 00000000000..ae399ced89f --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/internal/WebSocketSession.java @@ -0,0 +1,192 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.mockwebserver.internal; + +import io.fabric8.mockwebserver.MockServerException; +import io.fabric8.mockwebserver.dsl.HttpMethod; +import okhttp3.Response; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; +import okhttp3.mockwebserver.RecordedRequest; +import okio.ByteString; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class WebSocketSession extends WebSocketListener { + + private final List open; + private final WebSocketMessage failure; + private final Exception cause; + + private final Collection activeSockets = ConcurrentHashMap.newKeySet(); + private final Collection pendingMessages = ConcurrentHashMap.newKeySet(); + private final Map> requestEvents = new HashMap<>(); + private final Map> sentWebSocketMessagesRequestEvents = new HashMap<>(); + private final Map> httpRequestEvents = new HashMap<>(); + private final List timedEvents = new ArrayList<>(); + + private final ScheduledExecutorService executor; + + public WebSocketSession(List open, WebSocketMessage failure, Exception cause) { + this.open = open; + this.failure = failure; + this.cause = cause; + this.executor = Executors.newScheduledThreadPool(1); + } + + @Override + public void onClosing(WebSocket webSocket, int code, String reason) { + webSocket.close(code, reason); + } + + @Override + public void onOpen(WebSocket webSocket, Response response) { + activeSockets.add(webSocket); + //Schedule all timed events + for (WebSocketMessage msg : open) { + send(webSocket, msg); + } + + for (WebSocketMessage msg : timedEvents) { + send(webSocket, msg); + } + closeActiveSocketsIfApplicable(); + } + + @Override + public void onMessage(WebSocket webSocket, ByteString bytes) { + onMessage(webSocket, bytes.utf8()); + } + + @Override + public void onMessage(WebSocket webSocket, String in) { + Queue queue = requestEvents.get(in); + send(webSocket, queue, in); + } + + @Override + public void onClosed(WebSocket webSocket, int code, String reason) { + activeSockets.remove(webSocket); + } + + private void send(WebSocket ws, Queue queue, String in) { + if (queue != null && !queue.isEmpty()) { + WebSocketMessage msg = queue.peek(); + send(ws, msg); + if (msg.isToBeRemoved()) { + queue.remove(); + } + checkIfShouldSendAgain(ws, msg); + } else { + ws.close(1002, "Unexpected message:" + in); + } + } + + private void checkIfShouldSendAgain(WebSocket ws, WebSocketMessage msg) { + String text = msg.isBinary() ? ByteString.of(msg.getBytes()).utf8() : msg.getBody(); + if (sentWebSocketMessagesRequestEvents.containsKey(text)) { + Queue queue = sentWebSocketMessagesRequestEvents.get(text); + send(ws, queue, text); + } + } + + public void dispatch(RecordedRequest request) { + HttpMethod method = HttpMethod.valueOf(request.getMethod()); + String path = request.getPath(); + SimpleRequest key = new SimpleRequest(method, path); + SimpleRequest keyForAnyMethod = new SimpleRequest(path); + if (httpRequestEvents.containsKey(key)) { + Queue queue = httpRequestEvents.get(key); + activeSockets.forEach(ws -> send(ws, queue, "from http " + path)); + } else if (httpRequestEvents.containsKey(keyForAnyMethod)) { + Queue queue = httpRequestEvents.get(keyForAnyMethod); + activeSockets.forEach(ws -> send(ws, queue, "from http " + path)); + } + } + + public List getOpen() { + return open; + } + + public WebSocketMessage getFailure() { + return failure; + } + + public Exception getCause() { + return cause; + } + + public Map> getRequestEvents() { + return requestEvents; + } + + public List getTimedEvents() { + return timedEvents; + } + + public Map> getSentWebSocketMessagesRequestEvents() { + return sentWebSocketMessagesRequestEvents; + } + + public Map> getHttpRequestEvents() { + return httpRequestEvents; + } + + private void send(final WebSocket ws, final WebSocketMessage message) { + final UUID id = UUID.randomUUID(); + pendingMessages.add(id); + executor.schedule(() -> { + if (ws != null) { + if (message.isBinary()) { + ws.send(ByteString.of(message.getBytes())); + } else { + ws.send(message.getBody()); + } + pendingMessages.remove(id); + } + closeActiveSocketsIfApplicable(); + }, message.getDelay(), TimeUnit.MILLISECONDS); + } + + public void closeActiveSocketsIfApplicable() { + if (pendingMessages.isEmpty() && requestEvents.isEmpty() && httpRequestEvents.isEmpty() + && sentWebSocketMessagesRequestEvents.isEmpty()) { + activeSockets.forEach(ws -> ws.close(1000, "Closing...")); + } + } + + public void shutdown() { + try { + executor.shutdown(); + if (!executor.awaitTermination(1, TimeUnit.MINUTES)) { + executor.shutdownNow(); + } + } catch (InterruptedException e) { + throw MockServerException.launderThrowable(e); + } + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/BodyProvider.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/BodyProvider.java new file mode 100644 index 00000000000..1379f535223 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/BodyProvider.java @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.utils; + +import okhttp3.mockwebserver.RecordedRequest; + +/** + * A class that allows returning the body of a response given a certain request. + */ +public interface BodyProvider { + + T getBody(RecordedRequest request); + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/CertUtils.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/CertUtils.java new file mode 100644 index 00000000000..c8dde0df712 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/CertUtils.java @@ -0,0 +1,113 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.utils; + +import okio.ByteString; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.util.Base64; + +public class CertUtils { + + private CertUtils() { + } + + public static InputStream getInputStreamFromDataOrFile(String data, String file) throws FileNotFoundException { + if (data != null) { + final byte[] bytes; + ByteString decoded = ByteString.decodeBase64(data); + if (decoded != null) { + bytes = decoded.toByteArray(); + } else { + bytes = data.getBytes(); + } + + return new ByteArrayInputStream(bytes); + } + if (file != null) { + return new FileInputStream(file); + } + return null; + } + + public static KeyStore createKeyStore(InputStream certInputStream, InputStream keyInputStream, String clientKeyAlgo, + char[] clientKeyPassphrase) + throws IOException, CertificateException, NoSuchAlgorithmException, InvalidKeySpecException, KeyStoreException { + CertificateFactory certFactory = CertificateFactory.getInstance("X509"); + X509Certificate cert = (X509Certificate) certFactory.generateCertificate(certInputStream); + + byte[] keyBytes = decodeKey(keyInputStream); + + PrivateKey privateKey; + + KeyFactory keyFactory = KeyFactory.getInstance(clientKeyAlgo); + try { + // First let's try PKCS8 + privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); + } catch (InvalidKeySpecException e) { + // Otherwise try PKCS8 + RSAPrivateCrtKeySpec keySpec = PKCS1Util.decodePKCS1(keyBytes); + privateKey = keyFactory.generatePrivate(keySpec); + } + + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(null, clientKeyPassphrase); + + String alias = cert.getSubjectX500Principal().getName(); + keyStore.setKeyEntry(alias, privateKey, clientKeyPassphrase, new Certificate[] { cert }); + + return keyStore; + } + + public static KeyStore createKeyStore(String clientCertData, String clientCertFile, String clientKeyData, + String clientKeyFile, String clientKeyAlgo, char[] clientKeyPassphrase) + throws IOException, CertificateException, NoSuchAlgorithmException, InvalidKeySpecException, KeyStoreException { + try (InputStream certInputStream = getInputStreamFromDataOrFile(clientCertData, clientCertFile); + InputStream keyInputStream = getInputStreamFromDataOrFile(clientKeyData, clientKeyFile)) { + return createKeyStore(certInputStream, keyInputStream, clientKeyAlgo, clientKeyPassphrase); + } + } + + private static byte[] decodeKey(InputStream keyInputStream) throws IOException { + try (BufferedReader keyReader = new BufferedReader(new InputStreamReader(keyInputStream)); + ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + String line; + while ((line = keyReader.readLine()) != null) { + baos.write(line.trim().getBytes()); + } + return Base64.getDecoder().decode(baos.toByteArray()); + } + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/Closeables.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/Closeables.java new file mode 100644 index 00000000000..a5a43ff54c3 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/Closeables.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.utils; + +import java.io.Closeable; +import java.io.IOException; +import java.util.logging.Logger; + +public final class Closeables { + + private static final Logger LOGGER = Logger.getLogger(Closeables.class.getName()); + + private Closeables() { + //Utility class + } + + public static void closeQuietly(Closeable closeable) { + try { + closeable.close(); + } catch (IOException e) { + LOGGER.warning("Error while closing object:" + closeable + ". Ignoring."); + } + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/PKCS1Util.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/PKCS1Util.java new file mode 100644 index 00000000000..31c1c82bcfa --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/PKCS1Util.java @@ -0,0 +1,141 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.utils; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.security.spec.RSAPrivateCrtKeySpec; + +/** + * This code is inspired and taken over from net.auth.core:oauth + * (albeit in a highly stripped variation): + *

+ * Source is from http://oauth.googlecode.com/svn/code/java/ which is licensed + * under the APL (http://oauth.googlecode.com/svn/code/java/LICENSE.txt) + *

+ * All credits go to the original author (zhang) + * + * @author roland + * @since 30/09/15 + */ +class PKCS1Util { + + private PKCS1Util() { + } + + public static RSAPrivateCrtKeySpec decodePKCS1(byte[] keyBytes) throws IOException { + DerParser parser = new DerParser(keyBytes); + Asn1Object sequence = parser.read(); + sequence.validateSequence(); + parser = new DerParser(sequence.getValue()); + parser.read(); + + return new RSAPrivateCrtKeySpec(next(parser), next(parser), + next(parser), next(parser), + next(parser), next(parser), + next(parser), next(parser)); + } + + // ========================================================================================== + + private static BigInteger next(DerParser parser) throws IOException { + return parser.read().getInteger(); + } + + static class DerParser { + + private final InputStream in; + + DerParser(byte[] bytes) { + this.in = new ByteArrayInputStream(bytes); + } + + Asn1Object read() throws IOException { + int tag = in.read(); + + if (tag == -1) { + throw new IOException("Invalid DER: stream too short, missing tag"); + } + + int length = getLength(); + byte[] value = new byte[length]; + if (in.read(value) < length) { + throw new IOException("Invalid DER: stream too short, missing value"); + } + + return new Asn1Object(tag, value); + } + + private int getLength() throws IOException { + int i = in.read(); + if (i == -1) { + throw new IOException("Invalid DER: length missing"); + } + + if ((i & ~0x7F) == 0) { + return i; + } + + int num = i & 0x7F; + if (i >= 0xFF || num > 4) { + throw new IOException("Invalid DER: length field too big (" + + i + ")"); + } + + byte[] bytes = new byte[num]; + if (in.read(bytes) < num) { + throw new IOException("Invalid DER: length too short"); + } + + return new BigInteger(1, bytes).intValue(); + } + } + + static class Asn1Object { + + private final int type; + private final byte[] value; + private final int tag; + + public Asn1Object(int tag, byte[] value) { + this.tag = tag; + this.type = tag & 0x1F; + this.value = value; + } + + public byte[] getValue() { + return value; + } + + BigInteger getInteger() throws IOException { + if (type != 0x02) { + throw new IOException("Invalid DER: object is not integer"); //$NON-NLS-1$ + } + return new BigInteger(value); + } + + void validateSequence() throws IOException { + if (type != 0x10) { + throw new IOException("Invalid DER: not a sequence"); + } + if ((tag & 0x20) != 0x20) { + throw new IOException("Invalid DER: can't parse primitive entity"); + } + } + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/ResponseProvider.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/ResponseProvider.java new file mode 100644 index 00000000000..9e5ef4fa300 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/ResponseProvider.java @@ -0,0 +1,32 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.utils; + +import okhttp3.Headers; +import okhttp3.mockwebserver.RecordedRequest; + +/** + * A class that allows returning a response given a certain request. + */ +public interface ResponseProvider extends BodyProvider { + + int getStatusCode(RecordedRequest request); + + Headers getHeaders(); + + void setHeaders(Headers headers); + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/ResponseProviders.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/ResponseProviders.java new file mode 100644 index 00000000000..784176f6ba5 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/ResponseProviders.java @@ -0,0 +1,151 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.utils; + +import okhttp3.Headers; +import okhttp3.mockwebserver.RecordedRequest; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Useful methods for creating basic response providers. + */ +public class ResponseProviders { + + private ResponseProviders() { + } + + public static ResponseProvider of(int statusCode, R element) { + if (element != null) { + return new FixedResponseProvider<>(statusCode, element); + } + return null; + } + + public static ResponseProvider of(int statusCode, R element, Map map) { + if (element != null) { + return new FixedResponseProvider<>(statusCode, element, map); + } + return null; + } + + public static ResponseProvider> ofAll(int statusCode, R... elements) { + if (elements != null) { + return new FixedResponseProvider<>(statusCode, Arrays.asList(elements)); + } + return null; + } + + public static ResponseProvider of(final int statusCode, final BodyProvider bodyProvider) { + if (bodyProvider != null) { + return new ResponseProvider() { + private Headers headers = new Headers.Builder().build(); + + @Override + public int getStatusCode(RecordedRequest request) { + return statusCode; + } + + @Override + public R getBody(RecordedRequest request) { + return bodyProvider.getBody(request); + } + + @Override + public Headers getHeaders() { + return headers; + } + + @Override + public void setHeaders(Headers headers) { + this.headers = headers; + } + }; + } + return null; + } + + private static class FixedResponseProvider implements ResponseProvider { + + private final int statusCode; + private final T element; + private Headers headers; + + public FixedResponseProvider(int statusCode, T element) { + this(statusCode, element, Collections.emptyMap()); + } + + public FixedResponseProvider(int statusCode, T element, Map headers) { + this(statusCode, element, toHeaders(headers)); + } + + public FixedResponseProvider(int statusCode, T element, Headers headers) { + this.statusCode = statusCode; + this.element = element; + this.headers = headers; + } + + @Override + public T getBody(RecordedRequest request) { + return element; + } + + @Override + public int getStatusCode(RecordedRequest request) { + return statusCode; + } + + @Override + public Headers getHeaders() { + return headers; + } + + @Override + public void setHeaders(Headers headers) { + this.headers = headers; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + FixedResponseProvider that = (FixedResponseProvider) o; + + return element != null ? element.equals(that.element) : that.element == null; + + } + + @Override + public int hashCode() { + return element != null ? element.hashCode() : 0; + } + + private static Headers toHeaders(Map headers) { + final Headers.Builder builder = new Headers.Builder(); + for (Map.Entry entry : headers.entrySet()) { + builder.set(entry.getKey(), entry.getValue()); + } + return builder.build(); + } + } + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/SSLUtils.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/SSLUtils.java new file mode 100644 index 00000000000..e63e016d8fc --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/utils/SSLUtils.java @@ -0,0 +1,72 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import static io.fabric8.mockwebserver.utils.CertUtils.createKeyStore; + +public final class SSLUtils { + + private SSLUtils() { + //Utility + } + + public static SSLContext sslContext(KeyManager[] keyManagers, TrustManager[] trustManagers, boolean trustCerts) + throws KeyManagementException, NoSuchAlgorithmException { + if (trustManagers == null && trustCerts) { + trustManagers = new TrustManager[] { new X509TrustManager() { + public void checkClientTrusted(X509Certificate[] chain, String s) { + } + + public void checkServerTrusted(X509Certificate[] chain, String s) { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } }; + } + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyManagers, trustManagers, new SecureRandom()); + return sslContext; + } + + public static KeyManager[] keyManagers(InputStream certInputStream, InputStream keyInputStream, String algo, + String passphrase) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, CertificateException, + InvalidKeySpecException, IOException { + KeyStore keyStore = createKeyStore(certInputStream, keyInputStream, algo, passphrase.toCharArray()); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keyStore, passphrase.toCharArray()); + return kmf.getKeyManagers(); + } +} diff --git a/junit/mockwebserver/src/main/resources/ssl/fabric8-private-key.pem b/junit/mockwebserver/src/main/resources/ssl/fabric8-private-key.pem new file mode 100644 index 00000000000..b6bb69d0c55 --- /dev/null +++ b/junit/mockwebserver/src/main/resources/ssl/fabric8-private-key.pem @@ -0,0 +1,50 @@ +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDeWNPC4SJE8DKX +GU5JISsrY1nnI249vmO6x+pEflcGBqaReQehRUNeMFGje92jZk+0xh8NbNFf4Ofs +wJnSXHAupOI6CFERS2aym4IuGX24c7WvoMeH0I8/w8TJTEKNmWCcduaZx5z041gd +kQHQKetYJkzVhh5p2tbIsYlBLe/XGH3IzAVbeFd6GUDK32EyFNrSSOtMEOwbejmH +2wJysLFxGsJFySbnvyEdjDvTdGpNdqPRCU93K/BfRo1ycqSZiSObAh605Ddo3DMQ +lE0rk0im4BQXxwvaVuhbzszR8XIln8/QT5HysiDY5R2hgQq1yJtXtbL7yFGQSeNe +1CG9Gb1JNnHCdAkf+n9RFVoisjsn8MGcSCxpTs0G16Oia41nQaKLByqh4++aithh +Ucd96ujYnIceljMycpkL0VMXzZ7xwHHU+aHPRkRQsDzbf0x0b7MwQD5XkE1rYPzw +/TwJ6qhpPoxzlQ/H5hqEl9zdPpZyQLcfr5YluaRDSTAaR1QruqlWM2Zzy5iBthvx +7hrYNQ/Re5pbp+b4M7h1I6zvohrMiCbtvMQrWYZWGtOHPsW+tUXTQyb2vIYIGIZM +sLZzOijXn78/0IA07xuivqlQ/jmC6jNZAZCC5PRss9KQLWJWu9V3jEcK5dCstJv5 +eocZ6RWy8ShoL2fupp2jYCRota88mQIDAQABAoICAGCtnuYNnij7iAPLLQ7/LRYg +jObDsRuHvTVp16MQjCZCngqC5Z5pz3EU8Wp0YVq7Ec04mCfuONvHnxCCbl/Zca3W +Y8d39kfe0Ti4BVKmItQg+87xydB0DtVa+iXM0uNe3XMq//m9obGZaKbydiddEsex +X5c4SeEdFmcNSvDcWHzeWVMF4w5ytRaSBGox1sE/8CWfLzBT61XHP1yjDd1wlrbn +O7G8VP5PTMbcQucep1onS/OIaNUYddv3gWlSD9/ykVjFAzUERlOB63I6CZP45o4o +wJPWKIE3aLECqmxe35Mcee/JqVwtt7qXZNrkkROZtnHcv4ZbA5wJhKOm+USP/I0Z +K3iHDTOE++LTWNUIOaUXjiowJ6V4rXf8x3hftLz95RnN1rZWXV7T+OCCW+VduGaC +139UM9mEJn0W5DAmFCjpPHLHqfNupbnoi+nuTIuu9+0aqtMchbTSFmnIiEJOeyJ/ +JvONLhB39XT08QkAf7IKFiqLeWIy6E9IR4TdOO3KBMbjtJTaMkj6q8C8C4evFF04 +tuPPgT6UAA5TxihBAipHd1mIs/yTTGSZMMPb4vLFlw8cEJllC0qIbJpVc45YauDI +kXnhoXcrjEdTy/aMiXlnxAu/l/PkHVcuOCP5kCGIyHX0g/Ig3y8nseVgRZc8i9Kf +vKH8tOFfaUPq0s6WffABAoIBAQD7fDX+RsU7Mi9iFXqPSbbuCRz8yBG54DJDh3Vt ++Y4BzGqboUDxCvpTbpw7vy4R67upFZ0G6p3PLTEimOfSFp7/KH0Gije3b7yexRwM +GVxf+d+Im1cgPhzfqAF92CIjIWGUXGqOvVX7hMBkhDdqgsaINB2jpzJTv47HgXfp +7Lf3op94thJP+tbMDvRuM+a1l5VJgrytVIdUBI0FaPWULdm5z2Sndua65oUBsVP5 +eMRQqIT+9qwMAkONoxCjADyD/yAdA55e2lAH8DM3FDhXpf048XLun5c49PppvcbW +3vpm262oiBXdxuCadsAb2RZogvJ30fKOqZnt4yrt8PR0+HP5AoIBAQDiVrOI0ziE +hGazvQkB5Rqcx7fMmOZ0s3jsqJAbNrwwuZjY6vC2659XiqVcyNp4RanbvofsQSBs +zN4DF0Rx72S+8ELIbk+cZ0Jwkix03cRNNkKbiUrUKr+zrvQbVEi+NRbz11Leoqw4 +cEcykuF3bjQvdE4R72ckQPdXEv1z/bRrCNyZq2qxdD38scHHFjM8PC9t2dghMUpN +9pS3BTLEYZBCCZ6kxq4z45dDxqosX2OImtHnVecHAPf3xy48cjDau9E1hkgClEdk +MSjPIpYz3zg0qH9Ef3qVkDv+6VuBdE/j6B65HC8z3fTcwluPc+AfhYkHykxKcCdn +tR9Kd+7sOfWhAoIBAQCaQXNA+BnsmHjV+gTGNVn+ohpktzegQvOx1jnibit70O4n +bf7Om4Q2fudYAol4tpbSPQ6nemu386lq5k1z4So/qo8d3tQUMXaKEK+GgFvYBwXk +3hvQDClbysq3bUZrNAONpC48Rcii0afNQAhZzcOHMihoBJtrIVmr6C8sjmW9gMO+ +oDeVVXBBlH67xhwikMsiXw3qZ6nmkDAL/Hh+Hq2pOpwr2FPompNFGYc/w6LvMp75 +YUbgytay3y3KPc/gyzHgeiK/XbuvUtenVkDFCmzLa9aqpbt1VVbwW1bG39jKFL9t +W6PF+EI2nNZzfnIvQvsFIgNdHIztjOT9NEpOIUPJAoIBAHEOnd9aooCPIj3lzvoD +Vqe5mzW3qmXgwCZ2jIULcjVkf9TahiLYz18LAk62hWpOYepB4eNBJNE0BDHHDYlb +6xb1LGaxs1KMwcM5QLufis6Gq/7FNXuFXvyCB60fDLb2DeD/TYWn/B609ttsQvNF +OQv7LIQI8ZxKV0JHWhL2R4ivhIG9/i1lwxDWOdUYYb9U0NwuVKc/173Zza8eCZ3O +niBebcAg/iMtLAHO2nIPs8gojXDgl+YHtdUuyQmogH7CEl6KFK41IvQJGjldLWn7 +tjeXcvrkMndC9LUAG5UuZDmTWMVeLrXZyNX8v3+Iggs8yJX7luAX5ZcIAflQryeQ +TAECggEAIMqnk2FFxbbCR034TARA/n9XPY5XYufTpq9WtIaRuMvA3I5/oLKg65B9 +5XDCzwr0RiJR8pzlJ6Pmtm01rzNpNvzVOwIe3QS8F10nVLsrhDXB9bq55UtAUYZX +pNCO4qLC004YemEHKKp4NrRXquGcPvzJ67Ezl4f/E9rMvTdUjzhhZ80m+80adP4o +8MXBA/5BYBKLZRkEtyin3etVAvJM6/oUv4zREbod/sWyhFq3O2ka3rFhV0ymDEr6 +dphptKrzseopjAVi05DFIR7k1D3YN4NB7nt4N8JC5ucCYhCFq6juBO6bGHFGZ3t9 +Sqju3/8JhKlPzgcIeEtTEncKaJh9UA== diff --git a/junit/mockwebserver/src/main/resources/ssl/fabric8.crt b/junit/mockwebserver/src/main/resources/ssl/fabric8.crt new file mode 100644 index 00000000000..5b01aa30127 --- /dev/null +++ b/junit/mockwebserver/src/main/resources/ssl/fabric8.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFPzCCAycCFFa1f+dP0SR0nMoPfO+MrMRNfjHaMA0GCSqGSIb3DQEBCwUAMFwx +CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMRUwEwYDVQQHDAxEZWZh +dWx0IENpdHkxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0y +MTExMDMwODU0NTRaFw0zMTExMDEwODU0NTRaMFwxCzAJBgNVBAYTAkFVMRMwEQYD +VQQIDApTb21lLVN0YXRlMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAN5Y08LhIkTwMpcZTkkhKytjWecjbj2+Y7rH6kR+VwYGppF5B6FF +Q14wUaN73aNmT7TGHw1s0V/g5+zAmdJccC6k4joIURFLZrKbgi4Zfbhzta+gx4fQ +jz/DxMlMQo2ZYJx25pnHnPTjWB2RAdAp61gmTNWGHmna1sixiUEt79cYfcjMBVt4 +V3oZQMrfYTIU2tJI60wQ7Bt6OYfbAnKwsXEawkXJJue/IR2MO9N0ak12o9EJT3cr +8F9GjXJypJmJI5sCHrTkN2jcMxCUTSuTSKbgFBfHC9pW6FvOzNHxciWfz9BPkfKy +INjlHaGBCrXIm1e1svvIUZBJ417UIb0ZvUk2ccJ0CR/6f1EVWiKyOyfwwZxILGlO +zQbXo6JrjWdBoosHKqHj75qK2GFRx33q6Nichx6WMzJymQvRUxfNnvHAcdT5oc9G +RFCwPNt/THRvszBAPleQTWtg/PD9PAnqqGk+jHOVD8fmGoSX3N0+lnJAtx+vliW5 +pENJMBpHVCu6qVYzZnPLmIG2G/HuGtg1D9F7mlun5vgzuHUjrO+iGsyIJu28xCtZ +hlYa04c+xb61RdNDJva8hggYhkywtnM6KNefvz/QgDTvG6K+qVD+OYLqM1kBkILk +9Gyz0pAtYla71XeMRwrl0Ky0m/l6hxnpFbLxKGgvZ+6mnaNgJGi1rzyZAgMBAAEw +DQYJKoZIhvcNAQELBQADggIBAJ1tNTAnPgAbfhXVxtVnnNPFGsrmUgtBj0f8NsY3 +F0ODX50TIjbVLYp7j3u+dgZu9/ruTOHcGLywNi5mJWB+s27KJJn3nBFPmd9d/QIV +zmjn5IVvikXezEjECQOscwDhwpSbzHqLoieDTJntVUyaNctAZM1YOxVKO97pCDdw +tV74xDzdnI/4JQFQPfshD699r3dtU5ax/jiVCvqM5hTAJ2M/UVyQtxm3lKzMYLNu +77chlVf8/hTop9B6Q4tD6Ajj2KPxaHB7y+5lhci5Rvb2YLVDs0HLq8UJmoJW3FLw +slrjs0NerSWoz5JfhmOQ0N9E3NBdV/kGr27WUeSlNOYh5bqneDCX+hPrO/4NtvpG +WnnJX9W6S6e5GBFsNwQIB9SQCjj9zKWqgszS937HRd9gLmnOCPm7jbCO5uOjDo5q +0t+E20r9xv+4il1QV7tkGg13texGDR43aGzsSNQ66PXOwzeeCPkFzrSu1QFBh7LL +69VMJIbgm3ywYJjO0vIi0mW+kAiqcniIxbDTcCuEI0yuVLyRNaAe6kWWLMVaJLUw +V4TNAOT7x8ZYGQGjhz2DAImvXMwZTK2wRwyv8S11G+ebIIUb4EXGbMksjU6tTquq +ViHO3TGAKPTHIjCYdNT/ZGYQ/PHXLmaDGSOcoW8FPT9ROPxXRSNicNfzLJk/o4Im +AZC5 +-----END CERTIFICATE----- diff --git a/junit/mockwebserver/src/main/resources/ssl/fabric8.csr b/junit/mockwebserver/src/main/resources/ssl/fabric8.csr new file mode 100644 index 00000000000..ef0eea5121d --- /dev/null +++ b/junit/mockwebserver/src/main/resources/ssl/fabric8.csr @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEoTCCAokCAQAwXDELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx +FTATBgNVBAcMDERlZmF1bHQgQ2l0eTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3ljTwuEi +RPAylxlOSSErK2NZ5yNuPb5jusfqRH5XBgamkXkHoUVDXjBRo3vdo2ZPtMYfDWzR +X+Dn7MCZ0lxwLqTiOghREUtmspuCLhl9uHO1r6DHh9CPP8PEyUxCjZlgnHbmmcec +9ONYHZEB0CnrWCZM1YYeadrWyLGJQS3v1xh9yMwFW3hXehlAyt9hMhTa0kjrTBDs +G3o5h9sCcrCxcRrCRckm578hHYw703RqTXaj0QlPdyvwX0aNcnKkmYkjmwIetOQ3 +aNwzEJRNK5NIpuAUF8cL2lboW87M0fFyJZ/P0E+R8rIg2OUdoYEKtcibV7Wy+8hR +kEnjXtQhvRm9STZxwnQJH/p/URVaIrI7J/DBnEgsaU7NBtejomuNZ0GiiwcqoePv +morYYVHHfero2JyHHpYzMnKZC9FTF82e8cBx1Pmhz0ZEULA8239MdG+zMEA+V5BN +a2D88P08CeqoaT6Mc5UPx+YahJfc3T6WckC3H6+WJbmkQ0kwGkdUK7qpVjNmc8uY +gbYb8e4a2DUP0XuaW6fm+DO4dSOs76IazIgm7bzEK1mGVhrThz7FvrVF00Mm9ryG +CBiGTLC2czoo15+/P9CANO8bor6pUP45guozWQGQguT0bLPSkC1iVrvVd4xHCuXQ +rLSb+XqHGekVsvEoaC9n7qado2AkaLWvPJkCAwEAAaAAMA0GCSqGSIb3DQEBCwUA +A4ICAQCExP0WiJbGkhbpIRVN30seLat5upU3WauQy4fGeDKZAq37LguhzeHkWXtu +Rifb5fz8e7PTOz1fwjHJ8pBQsy5mRoMDXYdtyn6S6A2xGTPUYT82mN6BSJbwJDQm +Y4l4Lhg+7cEvqls+Mx9Dq0eSlM7hH7ezOl5c25U+lG74dHLT2gq5ornjdBk2JKnx +2c95646UomKJKVZtzfPLFRJhmVOr2ndkzooF1GlWXZsU57hflH0Y6argAqC+Y/Hu +AFqsm48Uwixex1FfX53aEFnZG1vkDYm48idGUDEa1QNqqC7Wt0qDM8iZtYaHoc9D +wOSD4KGOUOvzooqKmRzHRRRXfL/K3xzFOFAbxJf5YbVHmRGHEWbEXwnjhz1PHgmS +sXNtmVSt7/ycGKRUHyK4s2xIol45EaD7B+80st0fj0n5WGnpX0Wx/XxIepoD7/dG +H3HNjJD9UyGW3l2q6TojQrYLdTo+k9/CS6yMbbI++QyPlv/cnI1JpS/9+wvF8RrX +1AfWplKt+T8gOs64Ns7triUGD96IAqZfj46olQBN90BwCZ1BasneZyDYhClRCrfN +0znZT0cwgCs0q+UU+WmMcfBWO7ctKj3cz3+SmX+R16nTFi5Uuj3J9ED0V1o687jZ +YgtA3vz5F9lf9DaKJ/23GuA2X7HYWCUDiLtB2junYNJ0toJNJw== +-----END CERTIFICATE REQUEST----- diff --git a/junit/mockwebserver/src/main/resources/ssl/fabric8.pub b/junit/mockwebserver/src/main/resources/ssl/fabric8.pub new file mode 100644 index 00000000000..ba5c2260a5e --- /dev/null +++ b/junit/mockwebserver/src/main/resources/ssl/fabric8.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDeWNPC4SJE8DKXGU5JISsrY1nnI249vmO6x+pEflcGBqaReQehRUNeMFGje92jZk+0xh8NbNFf4OfswJnSXHAupOI6CFERS2aym4IuGX24c7WvoMeH0I8/w8TJTEKNmWCcduaZx5z041gdkQHQKetYJkzVhh5p2tbIsYlBLe/XGH3IzAVbeFd6GUDK32EyFNrSSOtMEOwbejmH2wJysLFxGsJFySbnvyEdjDvTdGpNdqPRCU93K/BfRo1ycqSZiSObAh605Ddo3DMQlE0rk0im4BQXxwvaVuhbzszR8XIln8/QT5HysiDY5R2hgQq1yJtXtbL7yFGQSeNe1CG9Gb1JNnHCdAkf+n9RFVoisjsn8MGcSCxpTs0G16Oia41nQaKLByqh4++aithhUcd96ujYnIceljMycpkL0VMXzZ7xwHHU+aHPRkRQsDzbf0x0b7MwQD5XkE1rYPzw/TwJ6qhpPoxzlQ/H5hqEl9zdPpZyQLcfr5YluaRDSTAaR1QruqlWM2Zzy5iBthvx7hrYNQ/Re5pbp+b4M7h1I6zvohrMiCbtvMQrWYZWGtOHPsW+tUXTQyb2vIYIGIZMsLZzOijXn78/0IA07xuivqlQ/jmC6jNZAZCC5PRss9KQLWJWu9V3jEcK5dCstJv5eocZ6RWy8ShoL2fupp2jYCRota88mQ== diff --git a/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/DefaultMockServerCrudTest.groovy b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/DefaultMockServerCrudTest.groovy new file mode 100644 index 00000000000..6e0840dec3b --- /dev/null +++ b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/DefaultMockServerCrudTest.groovy @@ -0,0 +1,142 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver + +import com.fasterxml.jackson.databind.ObjectMapper +import io.fabric8.mockwebserver.crud.CrudDispatcher +import okhttp3.MediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody +import okhttp3.mockwebserver.MockWebServer +import spock.lang.Shared +import spock.lang.Specification + +class DefaultMockServerCrudTest extends Specification { + + DefaultMockServer server + + @Shared + def client = new OkHttpClient() + + @Shared + def mapper = new ObjectMapper() + + def setup() { + server = new DefaultMockServer(new Context(), new MockWebServer(), new HashMap<>(), + new CrudDispatcher(new Context(), new UserAttributeExtractor(), new JsonResponseComposer()), false) + server.start() + } + + def cleanup() { + server.shutdown() + } + + def "get /, with empty store, should return 404"() { + when: + def result = client.newCall(new Request.Builder().url(server.url("/")).build()).execute() + + then: + assert result.code() == 404 + assert result.body().string() == "" + } + + def "get /, with one item, should return item"() { + given: + client.newCall(new Request.Builder().url(server.url("/")).post( + RequestBody.create(MediaType.parse("application/json"), + mapper.writeValueAsString(new User(1L, "user", true)))).build()). + execute() + + when: + def result = client.newCall(new Request.Builder().url(server.url("/")).build()).execute() + + then: + assert result.code() == 200 + assert result.body().string() == "{\"id\":1,\"username\":\"user\",\"enabled\":true}" + } + + def "get /, with multiple items, should return array"() { + given: + client.newCall(new Request.Builder().url(server.url("/")).post( + RequestBody.create(MediaType.parse("application/json"), + mapper.writeValueAsString(new User(1L, "user", true)))).build()). + execute() + client.newCall(new Request.Builder().url(server.url("/")).post( + RequestBody.create(MediaType.parse("application/json"), + mapper.writeValueAsString(new User(2L, "user-2", true)))).build()). + execute() + + when: + def result = client.newCall(new Request.Builder().url(server.url("/")).build()).execute() + + then: + assert result.code() == 200 + assert result.body().string() == + "[{\"id\":1,\"username\":\"user\",\"enabled\":true},{\"id\":2,\"username\":\"user-2\",\"enabled\":true}]" + } + + def "get /1, with existent item, should return item"() { + given: + client.newCall(new Request.Builder().url(server.url("/")).post( + RequestBody.create(MediaType.parse("application/json"), + mapper.writeValueAsString(new User(1L, "user", true)))).build()). + execute() + client.newCall(new Request.Builder().url(server.url("/")).post( + RequestBody.create(MediaType.parse("application/json"), + mapper.writeValueAsString(new User(2L, "user-2", true)))).build()). + execute() + + when: + def result = client.newCall(new Request.Builder().url(server.url("/1")).build()).execute() + + then: + assert result.code() == 200 + assert result.body().string() == "{\"id\":1,\"username\":\"user\",\"enabled\":true}" + } + + def "put /1, with missing item, should create item"() { + when: + def result = client.newCall(new Request.Builder().url(server.url("/1")).put( + RequestBody.create(MediaType.parse("application/json"), + mapper.writeValueAsString(new User(1L, "user-replaced", true)))).build()). + execute() + + then: + assert result.code() == 201 + assert result.body().string() == "{\"id\":1,\"username\":\"user-replaced\",\"enabled\":true}" + } + + def "put /1, with existent item, should replace item"() { + given: + client.newCall(new Request.Builder().url(server.url("/")).post( + RequestBody.create(MediaType.parse("application/json"), + mapper.writeValueAsString(new User(1L, "user", true)))).build()). + execute() + + when: + def result = client.newCall(new Request.Builder().url(server.url("/1")).put( + RequestBody.create(MediaType.parse("application/json"), + mapper.writeValueAsString(new User(1L, "user-replaced", true)))).build()). + execute() + + then: + assert result.code() == 202 + assert result.body().string() == "{\"id\":1,\"username\":\"user-replaced\",\"enabled\":true}" + def item = client.newCall(new Request.Builder().url(server.url("/1")).build()).execute() + assert item.body().string() == "{\"id\":1,\"username\":\"user-replaced\",\"enabled\":true}" + } +} diff --git a/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/DefaultMockServerHttpsTest.groovy b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/DefaultMockServerHttpsTest.groovy new file mode 100644 index 00000000000..35cb5ff0a4e --- /dev/null +++ b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/DefaultMockServerHttpsTest.groovy @@ -0,0 +1,45 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver + +import okhttp3.OkHttpClient +import spock.lang.Shared +import spock.lang.Specification + +class DefaultMockServerHttpsTest extends Specification { + + DefaultMockServer server + + @Shared + OkHttpClient client = new OkHttpClient() + + def setup() { + server = new DefaultMockServer(true) + server.start() + } + + def cleanup() { + server.shutdown() + } + + def "url, with path, returns URL with HTTPS protocol"() { + when: + def result = server.url("/") + + then: + assert result.startsWith("https://") + } +} diff --git a/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/DefaultMockServerTest.groovy b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/DefaultMockServerTest.groovy new file mode 100644 index 00000000000..93adec35972 --- /dev/null +++ b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/DefaultMockServerTest.groovy @@ -0,0 +1,668 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver + +import io.fabric8.mockwebserver.utils.ResponseProvider +import okhttp3.* +import okhttp3.mockwebserver.RecordedRequest +import okio.ByteString +import spock.lang.Shared +import spock.lang.Specification + +import java.util.concurrent.ArrayBlockingQueue +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicReference + +class DefaultMockServerTest extends Specification { + + DefaultMockServer server + + @Shared + OkHttpClient client = new OkHttpClient() + + def setup() { + server = new DefaultMockServer() + server.start() + } + + def cleanup() { + server.shutdown() + } + + def "getPort, should return a valid port"() { + when: + def result = server.getPort() + + then: + assert result > 0 + assert result <= 65535 + } + + def "getHostName, should return a valid host name"() { + when: + def result = server.getHostName() + + then: + assert !result.isBlank() + } + + def "toProxy, should return Proxy with the current HostName and Port"() { + when: + def result = server.toProxyAddress() + + then: + assert result.address() instanceof InetSocketAddress + assert ((InetSocketAddress)result.address()).getPort() == server.getPort() + assert ((InetSocketAddress)result.address()).getHostName() == server.getHostName() + } + + def "getRequestCount, with no requests, should return 0"() { + when: + def result = server.getRequestCount() + + then: + assert result == 0 + } + + def "getRequestCount, with multiple, should return valid request count"() { + given: + client.newCall(new Request.Builder().url(server.url("/")).get().build()).execute() + client.newCall(new Request.Builder().url(server.url("/one")).get().build()).execute() + client.newCall(new Request.Builder().url(server.url("/two")).get().build()).execute() + + when: + def result = server.getRequestCount() + + then: + assert result == 3 + } + + def "getLastRequest, with no requests, should return null"() { + when: + def result = server.getLastRequest() + + then: + assert result == null + } + + def "getLastRequest, with one request, should return the request"() { + given: + client.newCall(new Request.Builder().url(server.url("/")).get().build()).execute() + + when: + def result = server.getLastRequest() + + then: + assert result.getPath() == "/" + } + + def "getLastRequest, with one request, can be invoked multiple times"() { + given: + client.newCall(new Request.Builder().url(server.url("/")).get().build()).execute() + server.getLastRequest() + + when: + def result = server.getLastRequest() + + then: + assert result.getPath() == "/" + } + + def "getLastRequest, with multiple requests, should return the latest request"() { + given: + client.newCall(new Request.Builder().url(server.url("/")).get().build()).execute() + client.newCall(new Request.Builder().url(server.url("/one")).get().build()).execute() + client.newCall(new Request.Builder().url(server.url("/two")).get().build()).execute() + + when: + def result = server.getLastRequest() + + then: + assert result.getPath() == "/two" + } + + def "getLastRequest, with multiple requests, can be invoked multiple times"() { + given: + client.newCall(new Request.Builder().url(server.url("/")).get().build()).execute() + client.newCall(new Request.Builder().url(server.url("/one")).get().build()).execute() + server.getLastRequest() + client.newCall(new Request.Builder().url(server.url("/two")).get().build()).execute() + server.getLastRequest() + + when: + def result = server.getLastRequest() + + then: + assert result.getPath() == "/two" + } + + def "takeRequest, with timeout and no requests, should return null and don't block (after timeout)"() { + when: + def result = server.takeRequest(1, TimeUnit.MICROSECONDS) + + then: + assert result == null + } + + def "when setting an expectation with once it should be met only the first time"() { + given: + server.expect().get().withPath("/api/v1/users").andReturn(200, "admin").once() + + when: + Request request = new Request.Builder().url(server.url("/api/v1/users")).get().build() + Response response1 = client.newCall(request).execute() + Response response2 = client.newCall(request).execute() + + then: + assert response1.code() == 200 + assert response1.body().string() == "admin" + assert response2.code() == 404 + + cleanup: + response1.close() + response2.close() + } + + def "when setting an expectation with n-th times it should be met only the for the first n-th times"() { + given: + server.expect().get().withPath("/api/v1/users").andReturn(200, "admin").times(3) + + when: + Request request = new Request.Builder().url(server.url("/api/v1/users")).get().build() + Response response1 = client.newCall(request).execute() + Response response2 = client.newCall(request).execute() + Response response3 = client.newCall(request).execute() + Response response4 = client.newCall(request).execute() + + then: + assert response1.code() == 200 + assert response1.body().string() == "admin" + assert response2.code() == 200 + assert response2.body().string() == "admin" + assert response3.code() == 200 + assert response3.body().string() == "admin" + assert response4.code() == 404 + + cleanup: + response1.close() + response2.close() + response3.close() + response4.close() + } + + def "when setting an expectation with always it should be met only always"() { + given: + server.expect().get().withPath("/api/v1/users").andReturn(200, "admin").always() + + when: + Request request = new Request.Builder().url(server.url("/api/v1/users")).get().build() + Response response1 = client.newCall(request).execute() + Response response2 = client.newCall(request).execute() + Response response3 = client.newCall(request).execute() + Response response4 = client.newCall(request).execute() + + then: + assert response1.code() == 200 + assert response1.body().string() == "admin" + assert response2.code() == 200 + assert response2.body().string() == "admin" + assert response3.code() == 200 + assert response3.body().string() == "admin" + assert response4.code() == 200 + assert response4.body().string() == "admin" + + cleanup: + response1.close() + response2.close() + response3.close() + response4.close() + } + + def "when setting an expectation as an object it should be serialized to json"() { + given: + User root = new User(0, "root", true) + + server.expect().get().withPath("/api/v1/users").andReturn(200, root).always() + + when: + Request request = new Request.Builder().url(server.url("/api/v1/users")).get().build() + Response response1 = client.newCall(request).execute() + + then: + assert response1.code() == 200 + assert response1.body().string() == "{\"id\":0,\"username\":\"root\",\"enabled\":true}" + + cleanup: + response1.close() + } + + def "when setting a timed websocket message it should be fire at the specified time"() { + given: + CountDownLatch closed = new CountDownLatch(1) + Queue messages = new ArrayBlockingQueue(1) + AtomicReference webSocketRef = new AtomicReference<>() + WebSocketListener listener = new WebSocketListener() { + @Override + void onMessage(WebSocket webSocket, String text) { + messages.add(text) + } + + @Override + void onMessage(WebSocket webSocket, ByteString bytes) { + onMessage(webSocket, bytes.utf8()) + } + + @Override + void onClosing(WebSocket webSocket, int code, String reason) { + webSocket.close(code, reason) + } + + @Override + void onClosed(WebSocket webSocket, int code, String reason) { + closed.countDown() + } + } + + server.expect().get().withPath("/api/v1/users/watch") + .andUpgradeToWebSocket() + .open() + .waitFor(1000).andEmit("DELETED") + .done() + .once() + + when: + Request request = new Request.Builder().url(server.url("/api/v1/users/watch")).get().build() + webSocketRef.set(client.newWebSocket(request, listener)) + + then: + messages.poll(10, TimeUnit.SECONDS) == "DELETED" + + when: + webSocketRef.get().close(1000, "just close") + + then: + closed.await(10, TimeUnit.SECONDS) + } + + def "when setting a request/response websocket message it should be fired when the event is triggered"() { + given: + CountDownLatch opened = new CountDownLatch(1) + CountDownLatch closed = new CountDownLatch(1) + CountDownLatch queued = new CountDownLatch(2) + Queue messages = new ArrayBlockingQueue(2) + AtomicReference webSocketRef = new AtomicReference<>() + + WebSocketListener listener = new WebSocketListener() { + @Override + void onOpen(WebSocket webSocket, Response response) { + webSocketRef.set(webSocket) + opened.countDown() + } + + @Override + void onMessage(WebSocket webSocket, String text) { + messages.add(text) + queued.countDown() + } + + @Override + void onMessage(WebSocket webSocket, ByteString bytes) { + onMessage(webSocket, bytes.utf8()) + } + + @Override + void onClosing(WebSocket webSocket, int code, String reason) { + webSocket.close(code, reason) + } + + @Override + void onClosed(WebSocket webSocket, int code, String reason) { + closed.countDown() + } + } + + server.expect().get().withPath("/api/v1/users/watch") + .andUpgradeToWebSocket() + .open() + .expect("create root").andEmit("CREATED").once() + .expect("delete root").andEmit("DELETED").once() + .done() + .once() + + + when: + Request request = new Request.Builder().url(server.url("/api/v1/users/watch")).get().build() + webSocketRef.set(client.newWebSocket(request, listener)) + + then: + opened.await(10, TimeUnit.SECONDS) + WebSocket ws = webSocketRef.get() + ws.send("create root") + ws.send("delete root") + queued.await(10, TimeUnit.SECONDS) + messages.poll(10, TimeUnit.SECONDS) == "CREATED" + messages.poll(10, TimeUnit.SECONDS) == "DELETED" + + when: + ws.close(1000, "just close") + + then: + closed.await(10, TimeUnit.SECONDS) + } + + def "when receiving an unexpected websocket message it should close the connection with status code 1002"() { + given: + CountDownLatch opened = new CountDownLatch(1) + CountDownLatch closed = new CountDownLatch(1) + int closeCode = -1 + String closeReason = null + AtomicReference webSocketRef = new AtomicReference<>() + + WebSocketListener listener = new WebSocketListener() { + @Override + void onOpen(WebSocket webSocket, Response response) { + webSocketRef.set(webSocket) + opened.countDown() + } + + @Override + void onMessage(WebSocket webSocket, String text) { + System.out.println(text) + } + + @Override + void onMessage(WebSocket webSocket, ByteString bytes) { + onMessage(webSocket, bytes.utf8()) + } + + @Override + void onClosing(WebSocket webSocket, int code, String reason) { + System.out.println("Closing: " + code + " : " + reason) + webSocket.close(code, reason) + } + + @Override + void onClosed(WebSocket webSocket, int code, String reason) { + closeCode = code + closeReason = reason + closed.countDown() + } + } + + server.expect().get().withPath("/api/v1/users/watch") + .andUpgradeToWebSocket() + .open() + .expect("expected message").andEmit("MESSAGE OK").once() + .done() + .once() + + when: + Request request = new Request.Builder().url(server.url("/api/v1/users/watch")).get().build() + webSocketRef.set(client.newWebSocket(request, listener)) + + then: + opened.await(10, TimeUnit.SECONDS) + WebSocket ws = webSocketRef.get() + ws.send("unexpected message") + closed.await(10, TimeUnit.SECONDS) + assert closeCode == 1002 + assert closeReason == "Unexpected message:unexpected message" + + } + + def "when setting a delayed response it should be delayed for the specified duration"() { + given: + server.expect().get().withPath("/api/v1/users").delay(100, TimeUnit.MILLISECONDS).andReturn(200, "admin").once() + + when: + Request request = new Request.Builder().url(server.url("/api/v1/users")).get().build() + long startTime = System.currentTimeMillis() + Response response1 = client.newCall(request).execute() + + then: + assert response1.code() == 200 + assert response1.body().string() == "admin" + assert System.currentTimeMillis() - startTime >= 100 + + cleanup: + response1.close() + } + + def "when using a body provider it should work as for static responses"() { + given: + int[] counter = [0] + server.expect().get().withPath("/api/v1/users").andReply(200, {req -> "admin" + (counter[0]++)}).always() + + when: + Request request = new Request.Builder().url(server.url("/api/v1/users")).get().build() + Response response1 = client.newCall(request).execute() + Response response2 = client.newCall(request).execute() + + then: + assert response1.code() == 200 + assert response1.body().string() == "admin0" + assert response2.code() == 200 + assert response2.body().string() == "admin1" + + cleanup: + response1.close() + response2.close() + } + + def "when using a response provider it should work as for static responses"() { + given: + int[] counter = [0, 0] + server.expect().get().withPath("/api/v1/users").andReply(new ResponseProvider() { + private Headers headers = new Headers.Builder().build() + + int getStatusCode(RecordedRequest request) { + return 200 + (counter[0]++) + } + + Object getBody(RecordedRequest request) { + return "admin" + (counter[1]++) + } + + @Override + Headers getHeaders() { + return headers + } + + @Override + void setHeaders(Headers headers) { + this.headers = headers + } + }).always() + + when: + Request req = new Request.Builder().url(server.url("/api/v1/users")).get().build() + Response response1 = client.newCall(req).execute() + Response response2 = client.newCall(req).execute() + + then: + assert response1.code() == 200 + assert response1.body().string() == "admin0" + assert response2.code() == 201 + assert response2.body().string() == "admin1" + + cleanup: + response1.close() + response2.close() + } + + def "should be able to set headers on responses"() { + given: + server.expect().get().withPath("/api/v1/users").andReturn(200, "admin").withHeader("test: header").withHeader("test2", "header2").once() + + when: + Request request = new Request.Builder().url(server.url("/api/v1/users")).get().build() + Response response = client.newCall(request).execute() + + then: + assert response.code() == 200 + assert response.body().string() == "admin" + assert response.header("test") == "header" + assert response.header("test2") == "header2" + + cleanup: + response.close() + } + + def "when setting an httprequest/response websocket message it should be fired when the event is triggered"() { + given: + CountDownLatch opened = new CountDownLatch(1) + CountDownLatch closed = new CountDownLatch(1) + CountDownLatch queued = new CountDownLatch(2) + Queue messages = new ArrayBlockingQueue(2) + AtomicReference webSocketRef = new AtomicReference<>() + + WebSocketListener listener = new WebSocketListener() { + @Override + void onOpen(WebSocket webSocket, Response response) { + webSocketRef.set(webSocket) + opened.countDown() + } + + @Override + void onMessage(WebSocket webSocket, String text) { + messages.add(text) + queued.countDown() + } + + @Override + void onMessage(WebSocket webSocket, ByteString bytes) { + onMessage(webSocket, bytes.utf8()) + } + + @Override + void onClosing(WebSocket webSocket, int code, String reason) { + webSocket.close(code, reason) + } + + @Override + void onClosed(WebSocket webSocket, int code, String reason) { + closed.countDown() + } + } + + server.expect().get().withPath("/api/v1/users/watch") + .andUpgradeToWebSocket() + .open() + .expectHttpRequest("/api/v1/create").andEmit("CREATED").once() + .expectHttpRequest("/api/v1/delete").andEmit("DELETED").once() + .done() + .once() + + + when: + Request request = new Request.Builder().url(server.url("/api/v1/users/watch")).get().build() + webSocketRef.set(client.newWebSocket(request, listener)) + + then: + opened.await(10, TimeUnit.SECONDS) + WebSocket ws = webSocketRef.get() + + when: + request = new Request.Builder().url(server.url("/api/v1/create")).get().build() + client.newCall(request).execute() + + then: + messages.poll(10, TimeUnit.SECONDS) == "CREATED" + + when: + request = new Request.Builder().url(server.url("/api/v1/delete")).get().build() + client.newCall(request).execute() + + then: + messages.poll(10, TimeUnit.SECONDS) == "DELETED" + + when: + ws.close(1000, "just close") + + then: + closed.await(10, TimeUnit.SECONDS) + } + + def "when setting an sentWebSocketMessage/response websocket message it should be fired when the event is triggered"() { + given: + CountDownLatch opened = new CountDownLatch(1) + CountDownLatch closed = new CountDownLatch(1) + CountDownLatch queued = new CountDownLatch(2) + Queue messages = new ArrayBlockingQueue(2) + AtomicReference webSocketRef = new AtomicReference<>() + + WebSocketListener listener = new WebSocketListener() { + @Override + void onOpen(WebSocket webSocket, Response response) { + webSocketRef.set(webSocket) + opened.countDown() + } + + @Override + void onMessage(WebSocket webSocket, String text) { + messages.add(text) + queued.countDown() + } + + @Override + void onMessage(WebSocket webSocket, ByteString bytes) { + onMessage(webSocket, bytes.utf8()) + } + + @Override + void onClosing(WebSocket webSocket, int code, String reason) { + webSocket.close(code, reason) + } + + @Override + void onClosed(WebSocket webSocket, int code, String reason) { + closed.countDown() + } + } + + server.expect().get().withPath("/api/v1/users/watch") + .andUpgradeToWebSocket() + .open() + .expectHttpRequest("/api/v1/create").andEmit("CREATED").once() + .expectSentWebSocketMessage("CREATED").andEmit("DELETED").once() + .done() + .once() + + + when: + Request request = new Request.Builder().url(server.url("/api/v1/users/watch")).get().build() + webSocketRef.set(client.newWebSocket(request, listener)) + + then: + opened.await(10, TimeUnit.SECONDS) + WebSocket ws = webSocketRef.get() + + when: + request = new Request.Builder().url(server.url("/api/v1/create")).get().build() + client.newCall(request).execute() + + then: + messages.poll(10, TimeUnit.SECONDS) == "CREATED" + messages.poll(10, TimeUnit.SECONDS) == "DELETED" + + when: + ws.close(1000, "just close") + + then: + closed.await(10, TimeUnit.SECONDS) + } +} diff --git a/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/DefaultMockServerWebSocketTest.groovy b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/DefaultMockServerWebSocketTest.groovy new file mode 100644 index 00000000000..6433be20d2b --- /dev/null +++ b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/DefaultMockServerWebSocketTest.groovy @@ -0,0 +1,167 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver + +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import okhttp3.WebSocket +import okhttp3.WebSocketListener +import spock.lang.Shared +import spock.lang.Specification + +import java.util.concurrent.CompletableFuture +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.stream.Collectors +import java.util.stream.IntStream + +class DefaultMockServerWebSocketTest extends Specification { + + DefaultMockServer server + + @Shared + OkHttpClient client = new OkHttpClient() + + def setup() { + server = new DefaultMockServer() + server.start() + } + + def cleanup() { + server.shutdown() + } + + def "andUpgradeToWebSocket, with configured events, should emit events"() { + given: + server.expect() + .withPath("/websocket") + .andUpgradeToWebSocket().open().waitFor(10L).andEmit("A text message").done().always() + def future = new CompletableFuture() + when: + def ws = client.newWebSocket(new Request.Builder().url(server.url("/websocket")).build(), new WebSocketListener() { + @Override + void onMessage(WebSocket webSocket, String text) { + future.complete(text) + } + }) + then: + assert future.get(100L, TimeUnit.MILLISECONDS) == "A text message" + cleanup: + ws.close(1000, "Test finished") + } + + def "andUpgradeToWebSocket, with configured events, should emit onClose when done"() { + given: + server.expect() + .withPath("/websocket") + .andUpgradeToWebSocket().open().immediately().andEmit("event").done().always() + def future = new CompletableFuture() + when: + def ws = client.newWebSocket(new Request.Builder().url(server.url("/websocket")).build(), new WebSocketListener() { + @Override + void onClosing(WebSocket webSocket, int code, String reason) { + future.complete(reason) + } + }) + then: + assert future.get(100L, TimeUnit.MILLISECONDS) == "Closing..." + } + + def "andUpgradeToWebSocket, with no events, should emit onClose"() { + given: + server.expect() + .withPath("/websocket") + .andUpgradeToWebSocket().open().done().always() + def future = new CompletableFuture() + when: + def ws = client.newWebSocket(new Request.Builder().url(server.url("/websocket")).build(), new WebSocketListener() { + @Override + void onClosing(WebSocket webSocket, int code, String reason) { + future.complete(reason) + } + }) + then: + assert future.get(100L, TimeUnit.MILLISECONDS) == "Closing..." + } + + // https://github.com/fabric8io/mockwebserver/pull/66#issuecomment-944289335 + def "andUpgradeToWebSocket, with multiple upgrades, should emit events for all websocket listeners"() { + given: + server.expect() + .withPath("/websocket") + .andUpgradeToWebSocket().open().waitFor(10L).andEmit("A text message").done().always() + def latch = new CountDownLatch(15) + def wsListener = new WebSocketListener() { + @Override + void onMessage(WebSocket webSocket, String text) { + latch.countDown() + } + } + when: + def wss = IntStream.range(0, 15).mapToObj(i -> + client.newWebSocket(new Request.Builder().url(server.url("/websocket")).build(), wsListener) + ).collect(Collectors.toList()) + then: + assert latch.await(10000L, TimeUnit.MILLISECONDS) + cleanup: + wss.forEach(ws -> ws.close(1000, "Test finished")) + } + + // https://github.com/fabric8io/mockwebserver/issues/77 + def "andUpgradeToWebSocket, with request header 'sec-websocket-protocol', should create response with matching header"() { + given: + server.expect() + .withPath("/websocket") + .andUpgradeToWebSocket().open().done().always() + def future = new CompletableFuture() + when: + def ws = client.newWebSocket(new Request.Builder().url(server.url("/websocket")).header("sec-websocket-protocol", "v4.channel.k8s.io").build(), new WebSocketListener() { + @Override + void onOpen(WebSocket webSocket, Response response) { + future.complete(response.header("sec-websocket-protocol")) + } + }) + then: + assert future.get(100L, TimeUnit.MILLISECONDS) == "v4.channel.k8s.io" + cleanup: + ws.close(1000, "Test finished") + } + + // https://github.com/fabric8io/mockwebserver/issues/77 + def "andUpgradeToWebSocket, with request header 'sec-websocket-protocol', should not change existing response header"() { + given: + server.expect() + .withPath("/websocket") + .andUpgradeToWebSocket() + .open() + .done() + .withHeader("sec-websocket-protocol", "v3.channel.k8s.io,v4.channel.k8s.io") + .always() + def future = new CompletableFuture() + when: + def ws = client.newWebSocket(new Request.Builder().url(server.url("/websocket")).header("sec-websocket-protocol", "v4.channel.k8s.io").build(), new WebSocketListener() { + @Override + void onOpen(WebSocket webSocket, Response response) { + future.complete(response.header("sec-websocket-protocol")) + } + }) + then: + assert future.get(100L, TimeUnit.MILLISECONDS) == "v3.channel.k8s.io,v4.channel.k8s.io" + cleanup: + ws.close(1000, "Test finished") + } +} diff --git a/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/JsonResponseComposer.groovy b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/JsonResponseComposer.groovy new file mode 100644 index 00000000000..9337a913de0 --- /dev/null +++ b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/JsonResponseComposer.groovy @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver + +import io.fabric8.mockwebserver.crud.ResponseComposer + +import java.util.stream.Collectors + +class JsonResponseComposer implements ResponseComposer { + @Override + String compose(Collection items) { + return "[" + items.stream().collect(Collectors.joining(",")) + "]" + } +} diff --git a/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/MockServerExceptionTest.groovy b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/MockServerExceptionTest.groovy new file mode 100644 index 00000000000..8e68b9d6dfe --- /dev/null +++ b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/MockServerExceptionTest.groovy @@ -0,0 +1,65 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver + +import spock.lang.Specification + +class MockServerExceptionTest extends Specification { + + def "launderThrowable, with null, should throw MockServerException"() { + when: + MockServerException.launderThrowable(null) + then: + def result = thrown(MockServerException) + assert result.getMessage() == "An error has occurred." + assert result.getCause() == null + } + + def "launderThrowable, with checked exception, should throw MockServerException"() { + when: + MockServerException.launderThrowable(new Exception("I'm checked")) + then: + def result = thrown(MockServerException) + assert result.getMessage() == "An error has occurred." + assert result.getCause().getClass() == Exception + assert result.getCause().getMessage() == "I'm checked" + } + + def "launderThrowable, with unchecked exception, should throw MockServerException"() { + when: + def result = MockServerException.launderThrowable(new RuntimeException("I'm unchecked")) + then: + assert result.getMessage() == "I'm unchecked" + assert result.getClass() != MockServerException + } + + def "launderThrowable, with Error, should not be handled"() { + when: + MockServerException.launderThrowable(new Error("I'm an Error")) + then: + def result = thrown(Error) + assert result.getMessage() == "I'm an Error" + } + + def "launderThrowable, with Interrupted Exception, should re-interrupt"() { + when: + MockServerException.launderThrowable(new InterruptedException("I'm interrupted")) + then: + assert Thread.currentThread().isInterrupted() + def result = thrown(MockServerException) + assert result.getMessage() == "An error has occurred." + } +} diff --git a/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/User.groovy b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/User.groovy new file mode 100644 index 00000000000..32132df8eb2 --- /dev/null +++ b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/User.groovy @@ -0,0 +1,86 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonProperty + +@JsonInclude(JsonInclude.Include.NON_NULL) +class User { + + @JsonProperty("id") + private Long id + + @JsonProperty("username") + private String username + + @JsonProperty("enabled") + private Boolean enabled + + private User() { + } + + public User(Long id, String username, Boolean enabled) { + this.id = id + this.username = username + this.enabled = enabled + } + + public Long getId() { + return id + } + + public void setId(Long id) { + this.id = id + } + + public String getUsername() { + return username + } + + public void setUsername(String username) { + this.username = username + } + + public Boolean getEnabled() { + return enabled + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled + } + + @Override + public boolean equals(Object o) { + if (this == o) return true + if (o == null || getClass() != o.getClass()) return false + + User user = (User) o + + if (id != null ? !id.equals(user.id) : user.id != null) return false + if (username != null ? !username.equals(user.username) : user.username != null) return false + return enabled != null ? enabled.equals(user.enabled) : user.enabled == null + + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0 + result = 31 * result + (username != null ? username.hashCode() : 0) + result = 31 * result + (enabled != null ? enabled.hashCode() : 0) + return result + } +} diff --git a/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/UserAttributeExtractor.groovy b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/UserAttributeExtractor.groovy new file mode 100644 index 00000000000..79c0033444f --- /dev/null +++ b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/UserAttributeExtractor.groovy @@ -0,0 +1,40 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver + +import com.fasterxml.jackson.databind.ObjectMapper +import io.fabric8.mockwebserver.crud.Attribute +import io.fabric8.mockwebserver.crud.AttributeExtractor +import io.fabric8.mockwebserver.crud.AttributeSet + +class UserAttributeExtractor implements AttributeExtractor { + + static def mapper = new ObjectMapper() + + @Override + AttributeSet fromPath(String path) { + if (path.trim().isBlank() || path.trim() == "/") { + return new AttributeSet(); + } + return new AttributeSet(new Attribute("id", path.substring(1))) + } + + @Override + AttributeSet fromResource(String resource) { + def user = mapper.readValue(resource, User) + return new AttributeSet(new Attribute("id", user.getId().toString())) + } +} diff --git a/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/crud/AttributeSetTest.groovy b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/crud/AttributeSetTest.groovy new file mode 100644 index 00000000000..a2990859d9a --- /dev/null +++ b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/crud/AttributeSetTest.groovy @@ -0,0 +1,164 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.crud + +import spock.lang.Specification + +class AttributeSetTest extends Specification { + + def "when two feature set are empty the should be equals"() { + given: + when: + AttributeSet f1 = new AttributeSet() + AttributeSet f2 = new AttributeSet() + then: + assert f1.equals(f2) + } + + def "when two feature sets contain the same feature"() { + given: + Attribute f = new Attribute("key1", "value1") + when: + AttributeSet f1 = new AttributeSet(f) + AttributeSet f2 = new AttributeSet(f) + then: + assert f1.equals(f2) + } + + def "when two feature sets contain the same features order should not matter"() { + given: + Attribute f1 = new Attribute("key1", "value1") + Attribute f2 = new Attribute("key2", "value2") + Attribute f3 = new Attribute("key3", "value3") + when: + AttributeSet fs12 = new AttributeSet(f1, f2) + AttributeSet fs21 = new AttributeSet(f2, f1) + AttributeSet fs23 = new AttributeSet(f2, f3) + + then: + assert fs12.equals(fs21) + assert !fs12.equals(fs23) + assert !fs21.equals(fs23) + } + + def "when an EXISTS attribute exists in both sets they should match"() { + given: + Attribute a1 = new Attribute("key1", "", AttributeType.EXISTS) + Attribute a2 = new Attribute("key2", "value2") + when: + AttributeSet selector = new AttributeSet(a1, a2) + AttributeSet attributeSet = new AttributeSet(a1, a2) + then: + assert attributeSet.matches(selector) + } + + def "when an EXISTS attribute exists in one set but not the other sets they should not match"() { + given: + Attribute a1 = new Attribute("key1", "", AttributeType.EXISTS) + Attribute a2 = new Attribute("key2", "value2") + when: + AttributeSet selector = new AttributeSet(a1, a2) + AttributeSet attributeSet = new AttributeSet(a2) + then: + assert !attributeSet.matches(selector) + } + + def "when a NOT_EXISTS attribute exists in both sets they should not match"() { + given: + Attribute a1 = new Attribute("key1", "", AttributeType.NOT_EXISTS) + Attribute a2 = new Attribute("key2", "value2") + when: + AttributeSet selector = new AttributeSet(a1, a2) + AttributeSet attributeSet = new AttributeSet(a1, a2) + then: + assert !attributeSet.matches(selector) + } + + def "when a NOT_EXISTS attribute exists in one set but not the other sets they should match"() { + given: + Attribute a1 = new Attribute("key1", "", AttributeType.NOT_EXISTS) + Attribute a2 = new Attribute("key2", "value2") + when: + AttributeSet selector = new AttributeSet(a1, a2) + AttributeSet attributeSet = new AttributeSet(a2) + then: + assert attributeSet.matches(selector) + } + + def "when multiple attributes are specified it should examine all"() { + given: + // Naming is important here as it controls the hashed order + Attribute a2 = new Attribute("key2", "value2") + Attribute a3 = new Attribute("key3", "", AttributeType.EXISTS) + when: + AttributeSet attributeSet = new AttributeSet(a2) + AttributeSet selectorWithOne = new AttributeSet(a2) + AttributeSet selectorWithTwo = new AttributeSet(a2, a3); + then: + + // Assert that the order is suitable for testing. The failing attribute should + // be in the *second* position to ensure we're examining all the values of the selector + assert new ArrayList<>(selectorWithTwo.attributes.values()).indexOf(a3) == 1; + + assert attributeSet.matches(selectorWithOne) + assert !attributeSet.matches(selectorWithTwo) + } + + def "when IN attribute in selector"() { + given: + Attribute a1 = new Attribute("key1", "value") + Attribute a2 = new Attribute("key2", "value1") + Attribute a3 = new Attribute("key2", "value2") + Attribute a4 = new Attribute("key2", "value3") + Attribute a5 = new Attribute("key2", Arrays.asList("value1", "value2"), AttributeType.IN) + + when: + AttributeSet attributeSetWithoutAttr = new AttributeSet(a1) + AttributeSet attributeSetWithVal1 = new AttributeSet(a2) + AttributeSet attributeSetWithVal2 = new AttributeSet(a3) + AttributeSet attributeSetWithWrongVal = new AttributeSet(a4) + AttributeSet selector = new AttributeSet(a5); + + then: + assert !attributeSetWithoutAttr.matches(selector) + assert attributeSetWithVal1.matches(selector) + assert attributeSetWithVal2.matches(selector) + assert !attributeSetWithWrongVal.matches(selector) + } + + def "when NOT_IN attribute in selector"() { + given: + Attribute a1 = new Attribute("key1", "value") + Attribute a2 = new Attribute("key2", "value1") + Attribute a3 = new Attribute("key2", "value2") + Attribute a4 = new Attribute("key2", "value3") + Attribute a5 = new Attribute("key2", Arrays.asList("value1", "value2"), AttributeType.NOT_IN) + + when: + AttributeSet attributeSetWithoutAttr = new AttributeSet(a1) + AttributeSet attributeSetWithVal1 = new AttributeSet(a2) + AttributeSet attributeSetWithVal2 = new AttributeSet(a3) + AttributeSet attributeSetWithWrongVal = new AttributeSet(a4) + AttributeSet selector = new AttributeSet(a5); + + then: + assert attributeSetWithoutAttr.matches(selector) + assert !attributeSetWithVal1.matches(selector) + assert !attributeSetWithVal2.matches(selector) + assert attributeSetWithWrongVal.matches(selector) + } + +} diff --git a/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/crud/AttributeTest.groovy b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/crud/AttributeTest.groovy new file mode 100644 index 00000000000..51adfd3468e --- /dev/null +++ b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/crud/AttributeTest.groovy @@ -0,0 +1,55 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.crud + +import spock.lang.Specification + +class AttributeTest extends Specification { + + def "when key and value equals features should equal"() { + given: + when: + Attribute f11 = new Attribute("key1", "value1") + Attribute f11a = new Attribute("key1", "value1") + Attribute f12 = new Attribute("key1", "value2") + Attribute f22 = new Attribute("key2", "value2") + + then: + assert !f11.equals(f22) + assert !f11.equals(f12) + assert f11.equals(f11a) + } + + def "equals, with same attributes but different type, should be true"() { + given: + Attribute a1 = new Attribute("key1", "value1", AttributeType.EXISTS) + Attribute a2 = new Attribute("key1", "value1", AttributeType.NOT_EXISTS) + when: + var result = a1 == a2 + then: + assert result + } + + def "hashCode, with same attributes but different type, should be equal"() { + given: + Attribute a1 = new Attribute("key1", "value1", AttributeType.EXISTS) + Attribute a2 = new Attribute("key1", "value1", AttributeType.NOT_EXISTS) + when: + var result = a1.hashCode() == a2.hashCode() + then: + assert result + } +} diff --git a/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/crud/CrudDispatcherTest.groovy b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/crud/CrudDispatcherTest.groovy new file mode 100644 index 00000000000..a7f920f1e0b --- /dev/null +++ b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/crud/CrudDispatcherTest.groovy @@ -0,0 +1,124 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.mockwebserver.crud + +import io.fabric8.mockwebserver.Context +import io.fabric8.mockwebserver.DefaultMockServer +import io.fabric8.mockwebserver.ServerRequest +import io.fabric8.mockwebserver.ServerResponse +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import okhttp3.RequestBody +import okhttp3.MediaType +import okhttp3.mockwebserver.MockWebServer +import spock.lang.Specification +import com.fasterxml.jackson.databind.JsonNode + +class CrudDispatcherTest extends Specification { + + AttributeExtractor extractor = new AttributeExtractor() { + + @Override + AttributeSet fromPath(String path) { + AttributeSet set = new AttributeSet() + + String[] parts = path.split("/") + if (parts.length > 2) { + set = set.add(new Attribute("namespace", parts[2])) + } + + if (parts.length > 4) { + set = set.add(new Attribute("name", parts[4])) + } + return set + } + + @Override + AttributeSet fromResource(String resource) { + return null + } + } + + ResponseComposer composer = new ResponseComposer() { + @Override + String compose(Collection items) { + StringBuilder sb = new StringBuilder(); + for (String item : items) { + sb.append(item).append(" ") + } + return sb.toString().trim() + } + } + + def "should be able to get after a patch"() { + given: + Context context = new Context() + DefaultMockServer server = new DefaultMockServer(context, new MockWebServer(), new HashMap>(), new CrudDispatcher(context, extractor, composer), false) + String startingJson = """{"foo":{"bar":"startingValue","baz":"keepThis"} }""" + String patch = """[{"op":"replace","path":"/foo/bar","value":"canary"}]""" + when: + server.start() + then: + OkHttpClient client = new OkHttpClient() + Request post = new Request.Builder().post(RequestBody.create(MediaType.parse("application/json"), startingJson)).url(server.url("/namespace/test/name/one")).build() + client.newCall(post).execute() + + Request patchRequest = new Request.Builder().patch(RequestBody.create(MediaType.parse("application/strategic-merge-patch+json"), patch)).url(server.url("/namespace/test/name/one")).build() + client.newCall(patchRequest).execute() + + Request get = new Request.Builder().get().url(server.url("/namespace/test/name/one")).build() + Response response = client.newCall(get).execute() + JsonNode responseJson = context.getMapper().readValue(response.body().string(), JsonNode.class); + JsonNode expected = context.mapper.readValue("""{"foo": {"bar": "canary", "baz": "keepThis"}}""", JsonNode.class) + expected == responseJson + } + + def "should be able to get after a post"() { + given: + Context context = new Context() + DefaultMockServer server = new DefaultMockServer(context, new MockWebServer(), new HashMap>(), new CrudDispatcher(context, extractor, composer), false) + when: + server.start() + then: + OkHttpClient client = new OkHttpClient() + Request post = new Request.Builder().post(RequestBody.create(MediaType.parse("text/html"), "one")).url(server.url("/namespace/test/name/one")).build() + client.newCall(post).execute() + Request get = new Request.Builder().get().url(server.url("/namespace/test/name/one")).build() + Response response = client.newCall(get).execute() + assert response.body().string().equals("one") + } + + def "should be able to delete after a post"() { + given: + Context context = new Context() + DefaultMockServer server = new DefaultMockServer(context, new MockWebServer(), new HashMap>(), new CrudDispatcher(context, extractor, composer), false) + when: + server.start() + then: + OkHttpClient client = new OkHttpClient() + Request post = new Request.Builder().post(RequestBody.create(MediaType.parse("text/html"), "one")).url(server.url("/namespace/test/name/one")).build() + client.newCall(post).execute() + Request get = new Request.Builder().delete().url(server.url("/namespace/test/name/one")).build() + Response response = client.newCall(get).execute() + assert response.successful + + Request getMissing = new Request.Builder().delete().url(server.url("/namespace/test/name/two")).build() + Response responseMissing = client.newCall(getMissing).execute() + assert !responseMissing.successful + } + +} diff --git a/pom.xml b/pom.xml index 528040720a9..22efbfb0c49 100644 --- a/pom.xml +++ b/pom.xml @@ -97,10 +97,10 @@ 1.15.0_1 2.15.2 11.0.16 - 0.2.2 3.9.4 3.9.0 4.4.5 + 0.3.0 3.0.2 @@ -113,6 +113,7 @@ 4.2.0 19.0.0 4.11.0 + 2.3-groovy-3.0 1.4.2_1 1.0.2 @@ -156,6 +157,7 @@ 3.1.1 3.6.0 3.9.0 + 3.0.0 0.0.3 1.2.3 2.4.3.Final @@ -203,6 +205,7 @@ kubernetes-model-generator kubernetes-client-api kubernetes-client + junit/mockwebserver junit/kubernetes-junit-jupiter junit/kubernetes-junit-jupiter-autodetected junit/kubernetes-server-mock @@ -271,7 +274,6 @@ io.fabric8 - kubernetes-model-autoscaling ${project.version} @@ -447,11 +449,6 @@ kubernetes-httpclient-okhttp ${project.version} - - com.squareup.okhttp3 - okhttp - ${okhttp.version} - io.fabric8 kubernetes-httpclient-jdk @@ -482,6 +479,11 @@ openshift-client ${project.version} + + io.fabric8 + mockwebserver + ${project.version} + io.fabric8 kubernetes-junit-jupiter @@ -730,6 +732,16 @@ ${sundrio.version} provided + + com.squareup.okhttp3 + okhttp + ${okhttp.version} + + + com.squareup.okhttp3 + mockwebserver + ${okhttp.version} + org.apache.commons commons-compress @@ -815,16 +827,9 @@ io.fabric8 - mockwebserver - ${mockwebserver.version} - - - com.sun - tools - - + zjsonpatch + ${zjsonpatch.version} - org.mockito mockito-core @@ -872,6 +877,13 @@ test + + org.spockframework + spock-core + ${spock.version} + test + + javax.validation validation-api