Skip to content

Commit 1212873

Browse files
committed
PLUGINAPI-42 Add unit tests and mutualize UrlPattern
1 parent 8c10020 commit 1212873

File tree

7 files changed

+257
-380
lines changed

7 files changed

+257
-380
lines changed

plugin-api/src/main/java/org/sonar/api/server/http/HttpRequest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
*/
2929
public interface HttpRequest {
3030

31-
3231
/**
3332
* Returns the port number to which the request was sent.
3433
*/

plugin-api/src/main/java/org/sonar/api/web/ServletFilter.java

Lines changed: 1 addition & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,12 @@
1919
*/
2020
package org.sonar.api.web;
2121

22-
import java.util.ArrayList;
23-
import java.util.Arrays;
24-
import java.util.Collection;
25-
import java.util.HashSet;
26-
import java.util.LinkedHashSet;
27-
import java.util.List;
28-
import java.util.Set;
29-
import java.util.function.Predicate;
30-
import java.util.stream.Collectors;
3122
import org.sonar.api.ExtensionPoint;
3223
import org.sonar.api.server.ServerSide;
3324

34-
import static java.util.Arrays.asList;
35-
import static java.util.Collections.unmodifiableList;
36-
import static org.apache.commons.lang.StringUtils.substringBeforeLast;
37-
import static org.sonar.api.utils.Preconditions.checkArgument;
38-
3925
/**
4026
* {@code @deprecated} since 9.16. Use {@link org.sonar.api.web.HttpFilter} instead.
27+
*
4128
* @since 3.1
4229
*/
4330
@ServerSide
@@ -51,166 +38,4 @@ public abstract class ServletFilter implements javax.servlet.Filter {
5138
public UrlPattern doGetPattern() {
5239
return UrlPattern.builder().build();
5340
}
54-
55-
public static final class UrlPattern {
56-
57-
private static final String MATCH_ALL = "/*";
58-
59-
private final List<String> inclusions;
60-
private final List<String> exclusions;
61-
private final Predicate<String>[] inclusionPredicates;
62-
private final Predicate<String>[] exclusionPredicates;
63-
64-
private UrlPattern(Builder builder) {
65-
this.inclusions = unmodifiableList(new ArrayList<>(builder.inclusions));
66-
this.exclusions = unmodifiableList(new ArrayList<>(builder.exclusions));
67-
if (builder.inclusionPredicates.isEmpty()) {
68-
// because Stream#anyMatch() returns false if stream is empty
69-
this.inclusionPredicates = new Predicate[]{s -> true};
70-
} else {
71-
this.inclusionPredicates = builder.inclusionPredicates.stream().toArray(Predicate[]::new);
72-
}
73-
this.exclusionPredicates = builder.exclusionPredicates.stream().toArray(Predicate[]::new);
74-
}
75-
76-
public boolean matches(String path) {
77-
return !Arrays.stream(exclusionPredicates).anyMatch(pattern -> pattern.test(path)) &&
78-
Arrays.stream(inclusionPredicates).anyMatch(pattern -> pattern.test(path));
79-
}
80-
81-
/**
82-
* @since 6.0
83-
*/
84-
public Collection<String> getInclusions() {
85-
return inclusions;
86-
}
87-
88-
/**
89-
* @since 6.0
90-
*/
91-
public Collection<String> getExclusions() {
92-
return exclusions;
93-
}
94-
95-
public String label() {
96-
return "UrlPattern{" +
97-
"inclusions=[" + convertPatternsToString(inclusions) + "]" +
98-
", exclusions=[" + convertPatternsToString(exclusions) + "]" +
99-
'}';
100-
}
101-
102-
private static String convertPatternsToString(List<String> input) {
103-
StringBuilder output = new StringBuilder();
104-
if (input.isEmpty()) {
105-
return "";
106-
}
107-
if (input.size() == 1) {
108-
return output.append(input.get(0)).toString();
109-
}
110-
return output.append(input.get(0)).append(", ...").toString();
111-
}
112-
113-
/**
114-
* Defines only a single inclusion pattern. This is a shortcut for {@code builder().includes(inclusionPattern).build()}.
115-
*/
116-
public static UrlPattern create(String inclusionPattern) {
117-
return builder().includes(inclusionPattern).build();
118-
}
119-
120-
/**
121-
* @since 6.0
122-
*/
123-
public static Builder builder() {
124-
return new Builder();
125-
}
126-
127-
/**
128-
* @since 6.0
129-
*/
130-
public static class Builder {
131-
private static final String WILDCARD_CHAR = "*";
132-
private static final Collection<String> STATIC_RESOURCES = unmodifiableList(asList(
133-
"*.css", "*.css.map", "*.ico", "*.png", "*.jpg", "*.jpeg", "*.gif", "*.svg", "*.js", "*.js.map", "*.pdf", "/json/*", "*.woff2",
134-
"/static/*", "/robots.txt", "/favicon.ico", "/apple-touch-icon*", "/mstile*"));
135-
136-
private final Set<String> inclusions = new LinkedHashSet<>();
137-
private final Set<String> exclusions = new LinkedHashSet<>();
138-
private final Set<Predicate<String>> inclusionPredicates = new HashSet<>();
139-
private final Set<Predicate<String>> exclusionPredicates = new HashSet<>();
140-
141-
private Builder() {
142-
}
143-
144-
public static Collection<String> staticResourcePatterns() {
145-
return STATIC_RESOURCES;
146-
}
147-
148-
/**
149-
* Add inclusion patterns. Supported formats are:
150-
* <ul>
151-
* <li>path prefixed by / and ended by * or /*, for example "/api/foo/*", to match all paths "/api/foo" and "api/api/foo/something/else"</li>
152-
* <li>path prefixed by / and ended by .*, for example "/api/foo.*", to match exact path "/api/foo" with any suffix like "/api/foo.protobuf"</li>
153-
* <li>path prefixed by *, for example "*\/foo", to match all paths "/api/foo" and "something/else/foo"</li>
154-
* <li>path with leading slash and no wildcard, for example "/api/foo", to match exact path "/api/foo"</li>
155-
* </ul>
156-
*/
157-
public Builder includes(String... includePatterns) {
158-
return includes(asList(includePatterns));
159-
}
160-
161-
/**
162-
* Add exclusion patterns. See format described in {@link #includes(String...)}
163-
*/
164-
public Builder includes(Collection<String> includePatterns) {
165-
this.inclusions.addAll(includePatterns);
166-
this.inclusionPredicates.addAll(includePatterns.stream()
167-
.filter(pattern -> !MATCH_ALL.equals(pattern))
168-
.map(Builder::compile)
169-
.collect(Collectors.toList()));
170-
return this;
171-
}
172-
173-
public Builder excludes(String... excludePatterns) {
174-
return excludes(asList(excludePatterns));
175-
}
176-
177-
public Builder excludes(Collection<String> excludePatterns) {
178-
this.exclusions.addAll(excludePatterns);
179-
this.exclusionPredicates.addAll(excludePatterns.stream()
180-
.map(Builder::compile)
181-
.collect(Collectors.toList()));
182-
return this;
183-
}
184-
185-
public UrlPattern build() {
186-
return new UrlPattern(this);
187-
}
188-
189-
private static Predicate<String> compile(String pattern) {
190-
int countStars = pattern.length() - pattern.replace(WILDCARD_CHAR, "").length();
191-
if (countStars == 0) {
192-
checkArgument(pattern.startsWith("/"), "URL pattern must start with slash '/': %s", pattern);
193-
return url -> url.equals(pattern);
194-
}
195-
checkArgument(countStars == 1, "URL pattern accepts only zero or one wildcard character '*': %s", pattern);
196-
if (pattern.charAt(0) == '/') {
197-
checkArgument(pattern.endsWith(WILDCARD_CHAR), "URL pattern must end with wildcard character '*': %s", pattern);
198-
if (pattern.endsWith("/*")) {
199-
String path = pattern.substring(0, pattern.length() - "/*".length());
200-
return url -> url.startsWith(path);
201-
}
202-
if (pattern.endsWith(".*")) {
203-
String path = pattern.substring(0, pattern.length() - ".*".length());
204-
return url -> substringBeforeLast(url, ".").equals(path);
205-
}
206-
String path = pattern.substring(0, pattern.length() - "*".length());
207-
return url -> url.startsWith(path);
208-
}
209-
checkArgument(pattern.startsWith(WILDCARD_CHAR), "URL pattern must start with wildcard character '*': %s", pattern);
210-
// remove the leading *
211-
String path = pattern.substring(1);
212-
return url -> url.endsWith(path);
213-
}
214-
}
215-
}
21641
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Sonar Plugin API
3+
* Copyright (C) 2009-2023 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.api.server.http;
21+
22+
import jakarta.servlet.http.HttpServletRequest;
23+
import java.util.Enumeration;
24+
import org.junit.Test;
25+
26+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
27+
import static org.mockito.Mockito.mock;
28+
import static org.mockito.Mockito.when;
29+
30+
public class JakartaHttpRequestTest {
31+
32+
@Test
33+
public void initRequest() {
34+
HttpServletRequest requestMock = mock(HttpServletRequest.class);
35+
when(requestMock.getServerPort()).thenReturn(80);
36+
when(requestMock.isSecure()).thenReturn(true);
37+
when(requestMock.getScheme()).thenReturn("https");
38+
when(requestMock.getServerName()).thenReturn("hostname");
39+
when(requestMock.getRequestURL()).thenReturn(new StringBuffer("https://hostname:80/path"));
40+
when(requestMock.getRequestURI()).thenReturn("/path");
41+
when(requestMock.getQueryString()).thenReturn("param1=value1");
42+
when(requestMock.getContextPath()).thenReturn("/path");
43+
when(requestMock.getMethod()).thenReturn("POST");
44+
when(requestMock.getParameter("param1")).thenReturn("value1");
45+
when(requestMock.getParameterValues("param1")).thenReturn(new String[]{"value1"});
46+
when(requestMock.getHeader("header1")).thenReturn("hvalue1");
47+
Enumeration<String> headers = mock(Enumeration.class);
48+
when(requestMock.getHeaders("header1")).thenReturn(headers);
49+
50+
JakartaHttpRequest request = new JakartaHttpRequest(requestMock);
51+
52+
assertThat(request.getRawRequest()).isSameAs(requestMock);
53+
assertThat(request.getServerPort()).isEqualTo(80);
54+
assertThat(request.isSecure()).isTrue();
55+
assertThat(request.getScheme()).isEqualTo("https");
56+
assertThat(request.getServerName()).isEqualTo("hostname");
57+
assertThat(request.getRequestURL()).isEqualTo("https://hostname:80/path");
58+
assertThat(request.getRequestURI()).isEqualTo("/path");
59+
assertThat(request.getQueryString()).isEqualTo("param1=value1");
60+
assertThat(request.getContextPath()).isEqualTo("/path");
61+
assertThat(request.getMethod()).isEqualTo("POST");
62+
assertThat(request.getParameter("param1")).isEqualTo("value1");
63+
assertThat(request.getParameterValues("param1")).containsExactly("value1");
64+
assertThat(request.getHeader("header1")).isEqualTo("hvalue1");
65+
assertThat(request.getHeaders("header1")).isEqualTo(headers);
66+
}
67+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Sonar Plugin API
3+
* Copyright (C) 2009-2023 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.api.server.http;
21+
22+
import jakarta.servlet.http.HttpServletResponse;
23+
import java.io.IOException;
24+
import java.io.PrintWriter;
25+
import java.util.List;
26+
import org.junit.Test;
27+
28+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
29+
import static org.mockito.Mockito.mock;
30+
import static org.mockito.Mockito.verify;
31+
import static org.mockito.Mockito.when;
32+
33+
public class JakartaHttpResponseTest {
34+
35+
@Test
36+
public void initResponse() throws IOException {
37+
HttpServletResponse responseMock = mock(HttpServletResponse.class);
38+
when(responseMock.getHeader("h1")).thenReturn("hvalue1");
39+
when(responseMock.getHeaders("h1")).thenReturn(List.of("hvalue1"));
40+
when(responseMock.getStatus()).thenReturn(200);
41+
PrintWriter writer = mock(PrintWriter.class);
42+
when(responseMock.getWriter()).thenReturn(writer);
43+
44+
JakartaHttpResponse response = new JakartaHttpResponse(responseMock);
45+
46+
assertThat(response.getRawResponse()).isSameAs(responseMock);
47+
assertThat(response.getHeader("h1")).isEqualTo("hvalue1");
48+
assertThat(response.getHeaders("h1")).asList().containsExactly("hvalue1");
49+
assertThat(response.getStatus()).isEqualTo(200);
50+
assertThat(response.getWriter()).isEqualTo(writer);
51+
52+
response.addHeader("h2", "hvalue2");
53+
response.setStatus(201);
54+
response.setContentType("text/plain");
55+
response.sendRedirect("http://redirect");
56+
verify(responseMock).addHeader("h2", "hvalue2");
57+
verify(responseMock).setStatus(201);
58+
verify(responseMock).setContentType("text/plain");
59+
verify(responseMock).sendRedirect("http://redirect");
60+
}
61+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Sonar Plugin API
3+
* Copyright (C) 2009-2023 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.api.server.http;
21+
22+
import java.util.Enumeration;
23+
import javax.servlet.http.HttpServletRequest;
24+
import org.junit.Test;
25+
26+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
27+
import static org.mockito.Mockito.mock;
28+
import static org.mockito.Mockito.when;
29+
30+
public class JavaxHttpRequestTest {
31+
32+
@Test
33+
public void initRequest() {
34+
HttpServletRequest requestMock = mock(HttpServletRequest.class);
35+
when(requestMock.getServerPort()).thenReturn(80);
36+
when(requestMock.isSecure()).thenReturn(true);
37+
when(requestMock.getScheme()).thenReturn("https");
38+
when(requestMock.getServerName()).thenReturn("hostname");
39+
when(requestMock.getRequestURL()).thenReturn(new StringBuffer("https://hostname:80/path"));
40+
when(requestMock.getRequestURI()).thenReturn("/path");
41+
when(requestMock.getQueryString()).thenReturn("param1=value1");
42+
when(requestMock.getContextPath()).thenReturn("/path");
43+
when(requestMock.getMethod()).thenReturn("POST");
44+
when(requestMock.getParameter("param1")).thenReturn("value1");
45+
when(requestMock.getParameterValues("param1")).thenReturn(new String[]{"value1"});
46+
when(requestMock.getHeader("header1")).thenReturn("hvalue1");
47+
Enumeration<String> headers = mock(Enumeration.class);
48+
when(requestMock.getHeaders("header1")).thenReturn(headers);
49+
50+
JavaxHttpRequest request = new JavaxHttpRequest(requestMock);
51+
52+
assertThat(request.getRawRequest()).isSameAs(requestMock);
53+
assertThat(request.getServerPort()).isEqualTo(80);
54+
assertThat(request.isSecure()).isTrue();
55+
assertThat(request.getScheme()).isEqualTo("https");
56+
assertThat(request.getServerName()).isEqualTo("hostname");
57+
assertThat(request.getRequestURL()).isEqualTo("https://hostname:80/path");
58+
assertThat(request.getRequestURI()).isEqualTo("/path");
59+
assertThat(request.getQueryString()).isEqualTo("param1=value1");
60+
assertThat(request.getContextPath()).isEqualTo("/path");
61+
assertThat(request.getMethod()).isEqualTo("POST");
62+
assertThat(request.getParameter("param1")).isEqualTo("value1");
63+
assertThat(request.getParameterValues("param1")).containsExactly("value1");
64+
assertThat(request.getHeader("header1")).isEqualTo("hvalue1");
65+
assertThat(request.getHeaders("header1")).isEqualTo(headers);
66+
}
67+
}

0 commit comments

Comments
 (0)