001/*
002 * Copyright 2012-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.test.web.client;
018
019import java.io.IOException;
020import java.io.OutputStream;
021import java.net.URI;
022import java.net.URISyntaxException;
023
024import org.springframework.boot.web.client.RootUriTemplateHandler;
025import org.springframework.http.client.ClientHttpRequest;
026import org.springframework.http.client.ClientHttpResponse;
027import org.springframework.http.client.support.HttpRequestWrapper;
028import org.springframework.mock.http.client.MockClientHttpRequest;
029import org.springframework.test.web.client.ExpectedCount;
030import org.springframework.test.web.client.MockRestServiceServer;
031import org.springframework.test.web.client.MockRestServiceServer.MockRestServiceServerBuilder;
032import org.springframework.test.web.client.RequestExpectationManager;
033import org.springframework.test.web.client.RequestMatcher;
034import org.springframework.test.web.client.ResponseActions;
035import org.springframework.test.web.client.SimpleRequestExpectationManager;
036import org.springframework.util.Assert;
037import org.springframework.web.client.RestTemplate;
038import org.springframework.web.util.UriTemplateHandler;
039
040/**
041 * {@link RequestExpectationManager} that strips the specified root URI from the request
042 * before verification. Can be used to simply test declarations when all REST calls start
043 * the same way. For example: <pre class="code">
044 * RestTemplate restTemplate = new RestTemplateBuilder().rootUri("http://example.com").build();
045 * MockRestServiceServer server = RootUriRequestExpectationManager.bindTo(restTemplate);
046 * server.expect(requestTo("/hello")).andRespond(withSuccess());
047 * restTemplate.getForEntity("/hello", String.class);
048 * </pre>
049 *
050 * @author Phillip Webb
051 * @since 1.4.0
052 * @see RootUriTemplateHandler
053 * @see #bindTo(RestTemplate)
054 * @see #forRestTemplate(RestTemplate, RequestExpectationManager)
055 */
056public class RootUriRequestExpectationManager implements RequestExpectationManager {
057
058        private final String rootUri;
059
060        private final RequestExpectationManager expectationManager;
061
062        public RootUriRequestExpectationManager(String rootUri,
063                        RequestExpectationManager expectationManager) {
064                Assert.notNull(rootUri, "RootUri must not be null");
065                Assert.notNull(expectationManager, "ExpectationManager must not be null");
066                this.rootUri = rootUri;
067                this.expectationManager = expectationManager;
068        }
069
070        @Override
071        public ResponseActions expectRequest(ExpectedCount count,
072                        RequestMatcher requestMatcher) {
073                return this.expectationManager.expectRequest(count, requestMatcher);
074        }
075
076        @Override
077        public ClientHttpResponse validateRequest(ClientHttpRequest request)
078                        throws IOException {
079                String uri = request.getURI().toString();
080                if (uri.startsWith(this.rootUri)) {
081                        request = replaceURI(request, uri.substring(this.rootUri.length()));
082                }
083                try {
084                        return this.expectationManager.validateRequest(request);
085                }
086                catch (AssertionError ex) {
087                        String message = ex.getMessage();
088                        String prefix = "Request URI expected:</";
089                        if (message != null && message.startsWith(prefix)) {
090                                throw new AssertionError("Request URI expected:<" + this.rootUri
091                                                + message.substring(prefix.length() - 1));
092                        }
093                        throw ex;
094                }
095        }
096
097        private ClientHttpRequest replaceURI(ClientHttpRequest request,
098                        String replacementUri) {
099                URI uri;
100                try {
101                        uri = new URI(replacementUri);
102                        if (request instanceof MockClientHttpRequest) {
103                                ((MockClientHttpRequest) request).setURI(uri);
104                                return request;
105                        }
106                        return new ReplaceUriClientHttpRequest(uri, request);
107                }
108                catch (URISyntaxException ex) {
109                        throw new IllegalStateException(ex);
110                }
111        }
112
113        @Override
114        public void verify() {
115                this.expectationManager.verify();
116        }
117
118        @Override
119        public void reset() {
120                this.expectationManager.reset();
121        }
122
123        /**
124         * Return a bound {@link MockRestServiceServer} for the given {@link RestTemplate},
125         * configured with {@link RootUriRequestExpectationManager} when possible.
126         * @param restTemplate the source REST template
127         * @return a configured {@link MockRestServiceServer}
128         */
129        public static MockRestServiceServer bindTo(RestTemplate restTemplate) {
130                return bindTo(restTemplate, new SimpleRequestExpectationManager());
131        }
132
133        /**
134         * Return a bound {@link MockRestServiceServer} for the given {@link RestTemplate},
135         * configured with {@link RootUriRequestExpectationManager} when possible.
136         * @param restTemplate the source REST template
137         * @param expectationManager the source {@link RequestExpectationManager}
138         * @return a configured {@link MockRestServiceServer}
139         */
140        public static MockRestServiceServer bindTo(RestTemplate restTemplate,
141                        RequestExpectationManager expectationManager) {
142                MockRestServiceServerBuilder builder = MockRestServiceServer.bindTo(restTemplate);
143                return builder.build(forRestTemplate(restTemplate, expectationManager));
144        }
145
146        /**
147         * Return {@link RequestExpectationManager} to be used for binding with the specified
148         * {@link RestTemplate}. If the {@link RestTemplate} is using a
149         * {@link RootUriTemplateHandler} then a {@link RootUriRequestExpectationManager} is
150         * returned, otherwise the source manager is returned unchanged.
151         * @param restTemplate the source REST template
152         * @param expectationManager the source {@link RequestExpectationManager}
153         * @return a {@link RequestExpectationManager} to be bound to the template
154         */
155        public static RequestExpectationManager forRestTemplate(RestTemplate restTemplate,
156                        RequestExpectationManager expectationManager) {
157                Assert.notNull(restTemplate, "RestTemplate must not be null");
158                UriTemplateHandler templateHandler = restTemplate.getUriTemplateHandler();
159                if (templateHandler instanceof RootUriTemplateHandler) {
160                        return new RootUriRequestExpectationManager(
161                                        ((RootUriTemplateHandler) templateHandler).getRootUri(),
162                                        expectationManager);
163                }
164                return expectationManager;
165        }
166
167        /**
168         * {@link ClientHttpRequest} wrapper to replace the request URI.
169         */
170        private static class ReplaceUriClientHttpRequest extends HttpRequestWrapper
171                        implements ClientHttpRequest {
172
173                private final URI uri;
174
175                ReplaceUriClientHttpRequest(URI uri, ClientHttpRequest request) {
176                        super(request);
177                        this.uri = uri;
178                }
179
180                @Override
181                public URI getURI() {
182                        return this.uri;
183                }
184
185                @Override
186                public OutputStream getBody() throws IOException {
187                        return getRequest().getBody();
188                }
189
190                @Override
191                public ClientHttpResponse execute() throws IOException {
192                        return getRequest().execute();
193                }
194
195                @Override
196                public ClientHttpRequest getRequest() {
197                        return (ClientHttpRequest) super.getRequest();
198                }
199
200        }
201
202}