Skip to content

Commit 4c4541e

Browse files
committed
clean up SparqlProxyControllerTest and move header stuff into SparqlProxyCleaningServiceTest
1 parent af688e9 commit 4c4541e

2 files changed

Lines changed: 144 additions & 41 deletions

File tree

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/**
2+
* The MIT License
3+
* Copyright © 2017 FAIR Data Team
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining a copy
6+
* of this software and associated documentation files (the "Software"), to deal
7+
* in the Software without restriction, including without limitation the rights
8+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
* copies of the Software, and to permit persons to whom the Software is
10+
* furnished to do so, subject to the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be included in
13+
* all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
* THE SOFTWARE.
22+
*/
23+
package org.fairdatateam.fairdatapoint.acceptance.search.sparql;
24+
25+
import jakarta.servlet.http.HttpServletRequest;
26+
import org.fairdatateam.fairdatapoint.api.controller.search.SparqlProxyCleaningService;
27+
import org.junit.jupiter.api.Test;
28+
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.http.HttpHeaders;
30+
import org.springframework.http.HttpRequest;
31+
import org.springframework.http.MediaType;
32+
import org.springframework.http.ResponseEntity;
33+
import org.springframework.http.client.ClientHttpResponse;
34+
import org.springframework.mock.http.client.MockClientHttpRequest;
35+
import org.springframework.mock.http.client.MockClientHttpResponse;
36+
import org.springframework.mock.web.MockHttpServletRequest;
37+
import org.springframework.test.context.TestPropertySource;
38+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
39+
import java.lang.reflect.InvocationTargetException;
40+
import java.lang.reflect.Method;
41+
import java.util.List;
42+
import java.util.function.Consumer;
43+
44+
import static org.assertj.core.api.Assertions.assertThat;
45+
46+
@SpringJUnitConfig(SparqlProxyCleaningService.class)
47+
@TestPropertySource(properties = {"openapi.title=Test API", "openapi.version=1.0"})
48+
public class SparqlProxyCleaningServiceTest {
49+
50+
private final SparqlProxyCleaningService cleaningService;
51+
52+
/**
53+
* Constructor
54+
*/
55+
@Autowired
56+
public SparqlProxyCleaningServiceTest(SparqlProxyCleaningService cleaningService) {
57+
this.cleaningService = cleaningService;
58+
}
59+
60+
@Test
61+
public void cleansRequestHeaders() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
62+
final String remoteIP = "1.2.3.4";
63+
// headers for the incoming request from a remote client
64+
HttpHeaders requestHeadersIncoming = new HttpHeaders();
65+
requestHeadersIncoming.setAccept(List.of(MediaType.APPLICATION_JSON));
66+
requestHeadersIncoming.setBearerAuth("dummy-token");
67+
MockHttpServletRequest requestIncoming = new MockHttpServletRequest();
68+
requestIncoming.setRemoteAddr(remoteIP);
69+
70+
// headers for the outgoing request to the upstream sparql endpoint
71+
HttpHeaders requestHeadersOutgoing = new HttpHeaders();
72+
73+
// get access to package-private method for testing
74+
Method methodUnderTest = SparqlProxyCleaningService.class.getDeclaredMethod(
75+
"cleanRequestHeadersFactory", HttpServletRequest.class, HttpHeaders.class
76+
);
77+
methodUnderTest.setAccessible(true);
78+
79+
// create callable using factory
80+
@SuppressWarnings("unchecked")
81+
Consumer<HttpHeaders> cleanRequestHeaders = (Consumer<HttpHeaders>) methodUnderTest.invoke(
82+
cleaningService, requestIncoming, requestHeadersIncoming);
83+
84+
// perform header clean-up
85+
cleanRequestHeaders.accept(requestHeadersOutgoing);
86+
87+
// check result
88+
assertThat(requestHeadersOutgoing.getAccept()).hasSize(1).contains(MediaType.APPLICATION_JSON);
89+
assertThat(requestHeadersOutgoing.containsKey(HttpHeaders.AUTHORIZATION)).isFalse();
90+
assertThat(requestHeadersOutgoing.get(SparqlProxyCleaningService.HEADER_X_FORWARDED_FOR))
91+
.hasSize(1).contains(remoteIP);
92+
}
93+
94+
@Test
95+
public void cleansResponseHeaders() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
96+
// mock headers "received" from the upstream sparql endpoint
97+
final HttpHeaders mockBackendResponseHeaders = new HttpHeaders();
98+
99+
// add custom header
100+
final String customHeaderName = "my-custom-header";
101+
mockBackendResponseHeaders.add(customHeaderName, "dummy");
102+
103+
// add standard hop-by-hop headers
104+
final List<String> hopByHopHeaders = SparqlProxyCleaningService.getHopByHopHeaders();
105+
hopByHopHeaders.forEach(header -> mockBackendResponseHeaders.add(header, "dummy"));
106+
107+
// add connection header (list is incomplete on purpose, e.g. it does not contain all hop-by-hop headers)
108+
mockBackendResponseHeaders.put(HttpHeaders.CONNECTION, List.of(customHeaderName, hopByHopHeaders.get(0)));
109+
110+
// add server header
111+
final String backendServerHeader = "mock backend sparql server 1.0";
112+
mockBackendResponseHeaders.add(HttpHeaders.SERVER, backendServerHeader);
113+
114+
// get access to package-private method for testing
115+
Method methodUnderTest = SparqlProxyCleaningService.class.getDeclaredMethod(
116+
"cleanResponse", HttpRequest.class, ClientHttpResponse.class
117+
);
118+
methodUnderTest.setAccessible(true);
119+
120+
try (final MockClientHttpResponse sparqlServerResponse = new MockClientHttpResponse()) {
121+
// clean the response
122+
@SuppressWarnings("unchecked")
123+
ResponseEntity<byte[]> cleanedResponse = (ResponseEntity<byte[]>) methodUnderTest.invoke(
124+
cleaningService, new MockClientHttpRequest(), sparqlServerResponse
125+
);
126+
127+
// check result
128+
final HttpHeaders cleanedResponseHeaders = cleanedResponse.getHeaders();
129+
System.out.println(cleanedResponseHeaders);
130+
assertThat(cleanedResponseHeaders).doesNotContainKey(customHeaderName);
131+
hopByHopHeaders.forEach(header -> assertThat(cleanedResponseHeaders).doesNotContainKey(header));
132+
assertThat(cleanedResponseHeaders).doesNotContainKey(HttpHeaders.CONNECTION);
133+
assertThat(cleanedResponseHeaders).doesNotContainEntry(HttpHeaders.SERVER, List.of(backendServerHeader));
134+
assertThat(cleanedResponseHeaders).containsEntry(HttpHeaders.SERVER, List.of("Test API 1.0"));
135+
}
136+
}
137+
}

src/test/java/org/fairdatateam/fairdatapoint/acceptance/search/sparql/SparqlProxyControllerTest.java

Lines changed: 7 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
import org.fairdatateam.fairdatapoint.Profiles;
2626
import org.fairdatateam.fairdatapoint.api.controller.search.SparqlProxyController;
27-
import org.fairdatateam.fairdatapoint.api.controller.search.SparqlProxyCleaningService;
2827
import org.junit.jupiter.api.BeforeEach;
2928
import org.junit.jupiter.api.Test;
3029
import org.springframework.beans.factory.annotation.Autowired;
@@ -44,14 +43,11 @@
4443
import org.springframework.web.client.RestTemplate;
4544
import org.springframework.web.util.UriComponentsBuilder;
4645
import java.net.URI;
47-
import java.util.List;
4846
import java.util.Map;
4947

5048
import static org.assertj.core.api.Assertions.assertThat;
5149
import static org.fairdatateam.fairdatapoint.api.controller.search.SparqlProxyController.*;
52-
import static org.fairdatateam.fairdatapoint.api.controller.search.SparqlProxyCleaningService.HEADER_X_FORWARDED_FOR;
5350
import static org.fairdatateam.fairdatapoint.api.controller.search.SparqlQueryValidator.MESSAGE_INVALID_QUERY;
54-
import static org.hamcrest.Matchers.matchesPattern;
5551
import static org.hamcrest.Matchers.startsWith;
5652
import static org.springframework.test.web.client.match.MockRestRequestMatchers.*;
5753
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
@@ -109,15 +105,17 @@ public void setup() {
109105
@Test
110106
@WithAnonymousUser
111107
public void unauthenticatedRequestsAreDeniedWithoutContactingRemoteSparqlServer() {
112-
// specify request with url query, but without authentication
108+
// specify url query
113109
URI uriWithQuery = UriComponentsBuilder
114110
.fromPath(path)
115111
.queryParam(PARAM_QUERY, EXAMPLE_QUERY)
116112
.build()
117113
.toUri();
118114

115+
// perform request
119116
MvcTestResult testResult = mockMvc.get().uri(uriWithQuery).accept(MediaType.APPLICATION_JSON).exchange();
120117

118+
// should be denied
121119
assertThat(testResult).hasStatus(HttpStatus.FORBIDDEN);
122120
}
123121

@@ -172,7 +170,7 @@ public void formPostWithSparqlUpdateContentTypeIsDenied() {
172170

173171
@Test
174172
public void getRequestWithMaliciousUrlQueryIsDenied() {
175-
// specify request with malicious url query (trying to update)
173+
// specify malicious url query (trying to update)
176174
URI uriWithMaliciousQuery = UriComponentsBuilder
177175
.fromPath(path)
178176
.queryParam(PARAM_QUERY, MALICIOUS_SPARQL_UPDATE)
@@ -216,38 +214,14 @@ public void rawPostWithMaliciousQueryIsDenied() {
216214

217215
@Test
218216
public void proxyForwardingWorksForGetRequests() {
219-
// todo: move the header cleaning checks to a dedicated SparqlProxyCleaningServiceTest class
220-
221-
// mock headers for response from mock backend sparql server
222-
final HttpHeaders mockBackendResponseHeaders = new HttpHeaders();
223-
224-
// add a custom header
225-
final String customHeaderName = "my-custom-header";
226-
mockBackendResponseHeaders.add(customHeaderName, "dummy");
227-
228-
// add standard hop-by-hop headers
229-
final List<String> hopByHopHeaders = SparqlProxyCleaningService.getHopByHopHeaders();
230-
hopByHopHeaders.forEach(header -> mockBackendResponseHeaders.add(header, "dummy"));
231-
232-
// connection list is incomplete on purpose (does not contain all hop-by-hop headers)
233-
mockBackendResponseHeaders.put(HttpHeaders.CONNECTION, List.of(customHeaderName, hopByHopHeaders.get(0)));
234-
235-
// add server header
236-
final String backendServerHeader = "mock backend sparql server 1.0";
237-
mockBackendResponseHeaders.add(HttpHeaders.SERVER, backendServerHeader);
238-
239217
// configure mock server for remote SPARQL endpoint
240218
this.mockBackendSparqlServer
241219
// startsWith is required, otherwise it will expect a url without (query) parameters
242220
.expect(requestTo(startsWith(TEST_SPARQL_ENDPOINT_URL)))
243221
.andExpect(method(HttpMethod.GET))
244-
// forwarded header should have been added to request
245-
.andExpect(header(HEADER_X_FORWARDED_FOR, matchesPattern(".+")))
246-
// authorization headers should have been removed from request
247-
.andExpect(headerDoesNotExist(HttpHeaders.AUTHORIZATION))
248-
.andRespond(withSuccess().headers(mockBackendResponseHeaders).body(mockJsonBody));
222+
.andRespond(withSuccess().body(mockJsonBody));
249223

250-
// specify request with url query and normal user (non-admin)
224+
// specify url with query
251225
URI uriWithQuery = UriComponentsBuilder
252226
.fromPath(path)
253227
.queryParam(PARAM_QUERY, EXAMPLE_QUERY)
@@ -258,22 +232,14 @@ public void proxyForwardingWorksForGetRequests() {
258232
MvcTestResult testResult = mockMvc.get()
259233
.uri(uriWithQuery)
260234
.accept(MediaType.APPLICATION_JSON)
261-
.header(HttpHeaders.AUTHORIZATION, "dummy")
262235
.exchange();
263236

264237
// check that mock server has received expected requests
265238
// (for some reason this hides exceptions from the RestClient, so comment out to debug)
266239
mockBackendSparqlServer.verify();
267240

268-
// check response headers
269-
assertThat(testResult).hasStatusOk();
270-
assertThat(testResult).doesNotContainHeader(customHeaderName);
271-
assertThat(testResult).doesNotContainHeader(HttpHeaders.CONNECTION);
272-
hopByHopHeaders.forEach(header -> assertThat(testResult).doesNotContainHeader(header));
273-
assertThat(testResult).containsHeader(HttpHeaders.SERVER);
274-
assertThat(testResult).headers().doesNotContainEntry(HttpHeaders.SERVER, List.of(backendServerHeader));
275-
276241
// verify that proxy returns json response body
242+
assertThat(testResult).hasStatusOk();
277243
assertThat(testResult).bodyJson().hasPath("head.vars").hasPath("results.bindings");
278244

279245
}

0 commit comments

Comments
 (0)