mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-23 06:29:10 +00:00
Add supportedVersionPredicate to ApiVersionConfigurer
Closes gh-35267
This commit is contained in:
+1
-1
@@ -76,7 +76,7 @@ public class ApiVersionTests {
|
||||
|
||||
DefaultApiVersionStrategy versionStrategy = new DefaultApiVersionStrategy(
|
||||
List.of(versionResolver), new SemanticApiVersionParser(),
|
||||
true, null, true, null);
|
||||
true, null, true, null, null);
|
||||
|
||||
RestTestClient client = RestTestClient.bindToController(new TestController())
|
||||
.configureServer(mockMvcBuilder -> mockMvcBuilder.setApiVersionStrategy(versionStrategy))
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ public class ApiVersionTests {
|
||||
|
||||
DefaultApiVersionStrategy versionStrategy = new DefaultApiVersionStrategy(
|
||||
List.of(request -> request.getHeader(header)), new SemanticApiVersionParser(),
|
||||
true, null, true, null);
|
||||
true, null, true, null, null);
|
||||
|
||||
MockMvc mockMvc = standaloneSetup(new PersonController())
|
||||
.setApiVersionStrategy(versionStrategy)
|
||||
|
||||
+13
-7
@@ -20,6 +20,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
@@ -50,6 +51,8 @@ public class DefaultApiVersionStrategy implements ApiVersionStrategy {
|
||||
|
||||
private final Set<Comparable<?>> detectedVersions = new TreeSet<>();
|
||||
|
||||
private final Predicate<Comparable<?>> supportedVersionPredicate;
|
||||
|
||||
private final @Nullable ApiVersionDeprecationHandler deprecationHandler;
|
||||
|
||||
|
||||
@@ -71,7 +74,8 @@ public class DefaultApiVersionStrategy implements ApiVersionStrategy {
|
||||
*/
|
||||
public DefaultApiVersionStrategy(
|
||||
List<ApiVersionResolver> versionResolvers, ApiVersionParser<?> versionParser,
|
||||
boolean versionRequired, @Nullable String defaultVersion, boolean detectSupportedVersions,
|
||||
boolean versionRequired, @Nullable String defaultVersion,
|
||||
boolean detectSupportedVersions, @Nullable Predicate<Comparable<?>> supportedVersionPredicate,
|
||||
@Nullable ApiVersionDeprecationHandler deprecationHandler) {
|
||||
|
||||
Assert.notEmpty(versionResolvers, "At least one ApiVersionResolver is required");
|
||||
@@ -82,9 +86,16 @@ public class DefaultApiVersionStrategy implements ApiVersionStrategy {
|
||||
this.versionRequired = (versionRequired && defaultVersion == null);
|
||||
this.defaultVersion = (defaultVersion != null ? versionParser.parseVersion(defaultVersion) : null);
|
||||
this.detectSupportedVersions = detectSupportedVersions;
|
||||
this.supportedVersionPredicate = initSupportedVersionPredicate(supportedVersionPredicate);
|
||||
this.deprecationHandler = deprecationHandler;
|
||||
}
|
||||
|
||||
private Predicate<Comparable<?>> initSupportedVersionPredicate(@Nullable Predicate<Comparable<?>> predicate) {
|
||||
return (predicate != null ? predicate :
|
||||
(version -> (this.supportedVersions.contains(version) ||
|
||||
this.detectSupportedVersions && this.detectedVersions.contains(version))));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public @Nullable Comparable<?> getDefaultVersion() {
|
||||
@@ -160,16 +171,11 @@ public class DefaultApiVersionStrategy implements ApiVersionStrategy {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSupportedVersion(requestVersion)) {
|
||||
if (!this.supportedVersionPredicate.test(requestVersion)) {
|
||||
throw new InvalidApiVersionException(requestVersion.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSupportedVersion(Comparable<?> requestVersion) {
|
||||
return (this.supportedVersions.contains(requestVersion) ||
|
||||
this.detectSupportedVersions && this.detectedVersions.contains(requestVersion));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDeprecations(Comparable<?> version, HttpServletRequest request, HttpServletResponse response) {
|
||||
if (this.deprecationHandler != null) {
|
||||
|
||||
+27
-12
@@ -17,6 +17,7 @@
|
||||
package org.springframework.web.accept;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -44,6 +45,13 @@ public class DefaultApiVersionStrategiesTests {
|
||||
assertThat(strategy.getDefaultVersion()).isEqualTo(parser.parseVersion(version));
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingRequiredVersion() {
|
||||
assertThatThrownBy(() -> validateVersion(null, apiVersionStrategy()))
|
||||
.isInstanceOf(MissingApiVersionException.class)
|
||||
.hasMessage("400 BAD_REQUEST \"API version is required.\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateSupportedVersion() {
|
||||
String version = "1.2";
|
||||
@@ -53,7 +61,7 @@ public class DefaultApiVersionStrategiesTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectUnsupportedVersion() {
|
||||
void validateUnsupportedVersion() {
|
||||
assertThatThrownBy(() -> validateVersion("1.2", apiVersionStrategy()))
|
||||
.isInstanceOf(InvalidApiVersionException.class)
|
||||
.hasMessage("400 BAD_REQUEST \"Invalid API version: '1.2.0'.\"");
|
||||
@@ -62,7 +70,7 @@ public class DefaultApiVersionStrategiesTests {
|
||||
@Test
|
||||
void validateDetectedVersion() {
|
||||
String version = "1.2";
|
||||
DefaultApiVersionStrategy strategy = apiVersionStrategy(null, true);
|
||||
DefaultApiVersionStrategy strategy = apiVersionStrategy(null, true, null);
|
||||
strategy.addMappedVersion(version);
|
||||
validateVersion(version, strategy);
|
||||
}
|
||||
@@ -76,30 +84,37 @@ public class DefaultApiVersionStrategiesTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingRequiredVersion() {
|
||||
assertThatThrownBy(() -> validateVersion(null, apiVersionStrategy()))
|
||||
.isInstanceOf(MissingApiVersionException.class)
|
||||
.hasMessage("400 BAD_REQUEST \"API version is required.\"");
|
||||
void validateSupportedWithPredicate() {
|
||||
SemanticApiVersionParser.Version parsedVersion = parser.parseVersion("1.2");
|
||||
validateVersion("1.2", apiVersionStrategy(null, false, version -> version.equals(parsedVersion)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateUnsupportedWithPredicate() {
|
||||
DefaultApiVersionStrategy strategy = apiVersionStrategy(null, false, version -> version.equals("1.2"));
|
||||
assertThatThrownBy(() -> validateVersion("1.2", strategy)).isInstanceOf(InvalidApiVersionException.class);
|
||||
}
|
||||
|
||||
private static DefaultApiVersionStrategy apiVersionStrategy() {
|
||||
return apiVersionStrategy(null, false);
|
||||
return apiVersionStrategy(null, false, null);
|
||||
}
|
||||
|
||||
private static DefaultApiVersionStrategy apiVersionStrategy(@Nullable String defaultVersion) {
|
||||
return apiVersionStrategy(defaultVersion, false);
|
||||
return apiVersionStrategy(defaultVersion, false, null);
|
||||
}
|
||||
|
||||
private static DefaultApiVersionStrategy apiVersionStrategy(
|
||||
@Nullable String defaultVersion, boolean detectSupportedVersions) {
|
||||
@Nullable String defaultVersion, boolean detectSupportedVersions,
|
||||
@Nullable Predicate<Comparable<?>> supportedVersionPredicate) {
|
||||
|
||||
return new DefaultApiVersionStrategy(
|
||||
List.of(request -> request.getParameter("api-version")),
|
||||
new SemanticApiVersionParser(), true, defaultVersion, detectSupportedVersions, null);
|
||||
List.of(request -> request.getParameter("api-version")), new SemanticApiVersionParser(),
|
||||
true, defaultVersion, detectSupportedVersions, supportedVersionPredicate, null);
|
||||
}
|
||||
|
||||
private void validateVersion(@Nullable String version, DefaultApiVersionStrategy strategy) {
|
||||
strategy.validateVersion(version != null ? parser.parseVersion(version) : null, request);
|
||||
Comparable<?> parsedVersion = (version != null ? parser.parseVersion(version) : null);
|
||||
strategy.validateVersion(parsedVersion, request);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
-8
@@ -20,6 +20,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@@ -52,6 +53,8 @@ public class DefaultApiVersionStrategy implements ApiVersionStrategy {
|
||||
|
||||
private final Set<Comparable<?>> detectedVersions = new TreeSet<>();
|
||||
|
||||
private final Predicate<Comparable<?>> supportedVersionPredicate;
|
||||
|
||||
private final @Nullable ApiVersionDeprecationHandler deprecationHandler;
|
||||
|
||||
|
||||
@@ -73,7 +76,8 @@ public class DefaultApiVersionStrategy implements ApiVersionStrategy {
|
||||
*/
|
||||
public DefaultApiVersionStrategy(
|
||||
List<ApiVersionResolver> versionResolvers, ApiVersionParser<?> versionParser,
|
||||
boolean versionRequired, @Nullable String defaultVersion, boolean detectSupportedVersions,
|
||||
boolean versionRequired, @Nullable String defaultVersion,
|
||||
boolean detectSupportedVersions, @Nullable Predicate<Comparable<?>> supportedVersionPredicate,
|
||||
@Nullable ApiVersionDeprecationHandler deprecationHandler) {
|
||||
|
||||
Assert.notEmpty(versionResolvers, "At least one ApiVersionResolver is required");
|
||||
@@ -84,9 +88,16 @@ public class DefaultApiVersionStrategy implements ApiVersionStrategy {
|
||||
this.versionRequired = (versionRequired && defaultVersion == null);
|
||||
this.defaultVersion = (defaultVersion != null ? versionParser.parseVersion(defaultVersion) : null);
|
||||
this.detectSupportedVersions = detectSupportedVersions;
|
||||
this.supportedVersionPredicate = initSupportedVersionPredicate(supportedVersionPredicate);
|
||||
this.deprecationHandler = deprecationHandler;
|
||||
}
|
||||
|
||||
private Predicate<Comparable<?>> initSupportedVersionPredicate(@Nullable Predicate<Comparable<?>> predicate) {
|
||||
return (predicate != null ? predicate :
|
||||
(version -> (this.supportedVersions.contains(version) ||
|
||||
this.detectSupportedVersions && this.detectedVersions.contains(version))));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public @Nullable Comparable<?> getDefaultVersion() {
|
||||
@@ -111,7 +122,7 @@ public class DefaultApiVersionStrategy implements ApiVersionStrategy {
|
||||
* considered supported, and use of this method is optional. However, if you
|
||||
* prefer to use only explicitly configured, supported versions, then set
|
||||
* {@code detectSupportedVersions} flag to {@code false}.
|
||||
* @param versions the supported versions to add
|
||||
* @param versions the supported versions to add
|
||||
* @see #addMappedVersion(String...)
|
||||
*/
|
||||
public void addSupportedVersion(String... versions) {
|
||||
@@ -161,16 +172,11 @@ public class DefaultApiVersionStrategy implements ApiVersionStrategy {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSupportedVersion(requestVersion)) {
|
||||
if (!this.supportedVersionPredicate.test(requestVersion)) {
|
||||
throw new InvalidApiVersionException(requestVersion.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSupportedVersion(Comparable<?> requestVersion) {
|
||||
return (this.supportedVersions.contains(requestVersion) ||
|
||||
this.detectSupportedVersions && this.detectedVersions.contains(requestVersion));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDeprecations(Comparable<?> version, ServerWebExchange exchange) {
|
||||
if (this.deprecationHandler != null) {
|
||||
|
||||
+16
-2
@@ -22,6 +22,7 @@ import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@@ -58,6 +59,8 @@ public class ApiVersionConfigurer {
|
||||
|
||||
private boolean detectSupportedVersions = true;
|
||||
|
||||
private @Nullable Predicate<Comparable<?>> supportedVersionPredicate;
|
||||
|
||||
private @Nullable ApiVersionDeprecationHandler deprecationHandler;
|
||||
|
||||
|
||||
@@ -178,6 +181,16 @@ public class ApiVersionConfigurer {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a {@link Predicate} to perform supported version checks with, in
|
||||
* effect taking over the supported version check and superseding the
|
||||
* {@link #addSupportedVersions} and {@link #detectSupportedVersions}.
|
||||
* @param predicate the predicate to use
|
||||
*/
|
||||
public void setSupportedVersionPredicate(@Nullable Predicate<Comparable<?>> predicate) {
|
||||
this.supportedVersionPredicate = predicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a handler to add handling for requests with a deprecated API
|
||||
* version. Typically, this involves sending hints and information about
|
||||
@@ -199,8 +212,9 @@ public class ApiVersionConfigurer {
|
||||
|
||||
DefaultApiVersionStrategy strategy = new DefaultApiVersionStrategy(this.versionResolvers,
|
||||
(this.versionParser != null ? this.versionParser : new SemanticApiVersionParser()),
|
||||
(this.versionRequired != null ? this.versionRequired : true),
|
||||
this.defaultVersion, this.detectSupportedVersions, this.deprecationHandler);
|
||||
(this.versionRequired != null ? this.versionRequired : true), this.defaultVersion,
|
||||
this.detectSupportedVersions, this.supportedVersionPredicate,
|
||||
this.deprecationHandler);
|
||||
|
||||
this.supportedVersions.forEach(strategy::addSupportedVersion);
|
||||
|
||||
|
||||
+26
-11
@@ -17,6 +17,7 @@
|
||||
package org.springframework.web.reactive.accept;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -45,10 +46,17 @@ public class DefaultApiVersionStrategiesTests {
|
||||
@Test
|
||||
void defaultVersionIsParsed() {
|
||||
String version = "1.2.3";
|
||||
ApiVersionStrategy strategy = apiVersionStrategy(version, false);
|
||||
ApiVersionStrategy strategy = apiVersionStrategy(version, false, null);
|
||||
assertThat(strategy.getDefaultVersion()).isEqualTo(parser.parseVersion(version));
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingRequiredVersion() {
|
||||
assertThatThrownBy(() -> validateVersion(null, apiVersionStrategy()))
|
||||
.isInstanceOf(MissingApiVersionException.class)
|
||||
.hasMessage("400 BAD_REQUEST \"API version is required.\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateSupportedVersion() {
|
||||
String version = "1.2";
|
||||
@@ -58,7 +66,7 @@ public class DefaultApiVersionStrategiesTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectUnsupportedVersion() {
|
||||
void validateUnsupportedVersion() {
|
||||
assertThatThrownBy(() -> validateVersion("1.2", apiVersionStrategy()))
|
||||
.isInstanceOf(InvalidApiVersionException.class)
|
||||
.hasMessage("400 BAD_REQUEST \"Invalid API version: '1.2.0'.\"");
|
||||
@@ -67,7 +75,7 @@ public class DefaultApiVersionStrategiesTests {
|
||||
@Test
|
||||
void validateDetectedVersion() {
|
||||
String version = "1.2";
|
||||
DefaultApiVersionStrategy strategy = apiVersionStrategy(null, true);
|
||||
DefaultApiVersionStrategy strategy = apiVersionStrategy(null, true, null);
|
||||
strategy.addMappedVersion(version);
|
||||
validateVersion(version, strategy);
|
||||
}
|
||||
@@ -81,26 +89,33 @@ public class DefaultApiVersionStrategiesTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingRequiredVersion() {
|
||||
assertThatThrownBy(() -> validateVersion(null, apiVersionStrategy()))
|
||||
.isInstanceOf(MissingApiVersionException.class)
|
||||
.hasMessage("400 BAD_REQUEST \"API version is required.\"");
|
||||
void validateSupportedWithPredicate() {
|
||||
SemanticApiVersionParser.Version parsedVersion = parser.parseVersion("1.2");
|
||||
validateVersion("1.2", apiVersionStrategy(null, false, version -> version.equals(parsedVersion)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateUnsupportedWithPredicate() {
|
||||
DefaultApiVersionStrategy strategy = apiVersionStrategy(null, false, version -> version.equals("1.2"));
|
||||
assertThatThrownBy(() -> validateVersion("1.2", strategy)).isInstanceOf(InvalidApiVersionException.class);
|
||||
}
|
||||
|
||||
private static DefaultApiVersionStrategy apiVersionStrategy() {
|
||||
return apiVersionStrategy(null, false);
|
||||
return apiVersionStrategy(null, false, null);
|
||||
}
|
||||
|
||||
private static DefaultApiVersionStrategy apiVersionStrategy(
|
||||
@Nullable String defaultVersion, boolean detectSupportedVersions) {
|
||||
@Nullable String defaultVersion, boolean detectSupportedVersions,
|
||||
@Nullable Predicate<Comparable<?>> supportedVersionPredicate) {
|
||||
|
||||
return new DefaultApiVersionStrategy(
|
||||
List.of(exchange -> exchange.getRequest().getQueryParams().getFirst("api-version")),
|
||||
parser, true, defaultVersion, detectSupportedVersions, null);
|
||||
parser, true, defaultVersion, detectSupportedVersions, supportedVersionPredicate, null);
|
||||
}
|
||||
|
||||
private void validateVersion(@Nullable String version, DefaultApiVersionStrategy strategy) {
|
||||
strategy.validateVersion(version != null ? parser.parseVersion(version) : null, exchange);
|
||||
Comparable<?> parsedVersion = (version != null ? parser.parseVersion(version) : null);
|
||||
strategy.validateVersion(parsedVersion, exchange);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -380,7 +380,7 @@ class RequestPredicatesTests {
|
||||
|
||||
private static DefaultApiVersionStrategy apiVersionStrategy() {
|
||||
return new DefaultApiVersionStrategy(
|
||||
List.of(exchange -> null), new SemanticApiVersionParser(), true, null, false, null);
|
||||
List.of(exchange -> null), new SemanticApiVersionParser(), true, null, false, null, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ public class VersionRequestConditionTests {
|
||||
private static DefaultApiVersionStrategy initVersionStrategy(@Nullable String defaultVersion) {
|
||||
return new DefaultApiVersionStrategy(
|
||||
List.of(exchange -> exchange.getRequest().getQueryParams().getFirst("api-version")),
|
||||
new SemanticApiVersionParser(), true, defaultVersion, false, null);
|
||||
new SemanticApiVersionParser(), true, defaultVersion, false, null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+15
-2
@@ -22,6 +22,7 @@ import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@@ -59,6 +60,8 @@ public class ApiVersionConfigurer {
|
||||
|
||||
private boolean detectSupportedVersions = true;
|
||||
|
||||
private @Nullable Predicate<Comparable<?>> supportedVersionPredicate;
|
||||
|
||||
private @Nullable ApiVersionDeprecationHandler deprecationHandler;
|
||||
|
||||
|
||||
@@ -179,6 +182,16 @@ public class ApiVersionConfigurer {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a {@link Predicate} to perform supported version checks with, in
|
||||
* effect taking over the supported version check and superseding the
|
||||
* {@link #addSupportedVersions} and {@link #detectSupportedVersions}.
|
||||
* @param predicate the predicate to use
|
||||
*/
|
||||
public void setSupportedVersionPredicate(@Nullable Predicate<Comparable<?>> predicate) {
|
||||
this.supportedVersionPredicate = predicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a handler to add handling for requests with a deprecated API
|
||||
* version. Typically, this involves sending hints and information about
|
||||
@@ -200,8 +213,8 @@ public class ApiVersionConfigurer {
|
||||
|
||||
DefaultApiVersionStrategy strategy = new DefaultApiVersionStrategy(this.versionResolvers,
|
||||
(this.versionParser != null ? this.versionParser : new SemanticApiVersionParser()),
|
||||
(this.versionRequired != null ? this.versionRequired : true),
|
||||
this.defaultVersion, this.detectSupportedVersions,
|
||||
(this.versionRequired != null ? this.versionRequired : true), this.defaultVersion,
|
||||
this.detectSupportedVersions, this.supportedVersionPredicate,
|
||||
this.deprecationHandler);
|
||||
|
||||
this.supportedVersions.forEach(strategy::addSupportedVersion);
|
||||
|
||||
+1
-1
@@ -280,7 +280,7 @@ class RequestPredicatesTests {
|
||||
private static ServerRequest serverRequest(String version) {
|
||||
|
||||
ApiVersionStrategy strategy = new DefaultApiVersionStrategy(
|
||||
List.of(exchange -> null), new SemanticApiVersionParser(), true, null, false, null);
|
||||
List.of(exchange -> null), new SemanticApiVersionParser(), true, null, false, null, null);
|
||||
|
||||
MockHttpServletRequest servletRequest =
|
||||
PathPatternsTestUtils.initRequest("GET", null, "/path", true,
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ public class VersionRequestConditionTests {
|
||||
private static DefaultApiVersionStrategy initVersionStrategy(@Nullable String defaultVersion) {
|
||||
return new DefaultApiVersionStrategy(
|
||||
List.of(request -> request.getParameter("api-version")),
|
||||
new SemanticApiVersionParser(), true, defaultVersion, false, null);
|
||||
new SemanticApiVersionParser(), true, defaultVersion, false, null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user