Add HTTP Service registrar tests

Closes gh-33992
This commit is contained in:
rstoyanchev
2025-03-31 08:48:11 +00:00
parent 42409e21fa
commit 1c0bcba587
10 changed files with 435 additions and 51 deletions

View File

@@ -31,13 +31,13 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.web.client.support.echo.EchoA;
import org.springframework.web.client.support.echo.EchoB;
import org.springframework.web.client.support.greeting.GreetingA;
import org.springframework.web.client.support.greeting.GreetingB;
import org.springframework.web.service.registry.AbstractHttpServiceRegistrar;
import org.springframework.web.service.registry.HttpServiceProxyRegistry;
import org.springframework.web.service.registry.ImportHttpServices;
import org.springframework.web.service.registry.echo.EchoA;
import org.springframework.web.service.registry.echo.EchoB;
import org.springframework.web.service.registry.greeting.GreetingA;
import org.springframework.web.service.registry.greeting.GreetingB;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -0,0 +1,195 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* 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
*
* https://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 org.springframework.web.service.registry;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.web.service.registry.HttpServiceGroup.ClientType;
import org.springframework.web.service.registry.echo.EchoA;
import org.springframework.web.service.registry.echo.EchoB;
import org.springframework.web.service.registry.greeting.GreetingA;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link AnnotationHttpServiceRegistrar}.
* @author Rossen Stoyanchev
*/
public class AnnotationHttpServiceRegistrarTests {
private static final String ECHO_GROUP = "echo";
private static final String GREETING_GROUP = "greeting";
private final TestGroupRegistry groupRegistry = new TestGroupRegistry();
private final TestAnnotationHttpServiceRegistrar registrar = new TestAnnotationHttpServiceRegistrar();
@Test
void basicListing() {
doRegister(ListingConfig.class);
assertGroups(StubGroup.ofListing(ECHO_GROUP, EchoA.class, EchoB.class));
}
@Test
void basicScan() {
doRegister(ScanConfig.class);
assertGroups(
StubGroup.ofPackageClasses(ECHO_GROUP, EchoA.class),
StubGroup.ofPackageClasses(GREETING_GROUP, GreetingA.class));
}
@Test
void containerWithClientType() {
doRegister(ContainerConfig.class);
assertGroups(
StubGroup.ofListing(ECHO_GROUP, ClientType.WEB_CLIENT, EchoA.class),
StubGroup.ofListing(GREETING_GROUP, ClientType.WEB_CLIENT, GreetingA.class));
}
private void doRegister(Class<?> configClass) {
AnnotationMetadata metadata = AnnotationMetadata.introspect(configClass);
this.registrar.registerHttpServices(this.groupRegistry, metadata);
}
private void assertGroups(StubGroup... expectedGroups) {
Map<String, StubGroup> groupMap = this.groupRegistry.groupMap();
assertThat(groupMap.size()).isEqualTo(expectedGroups.length);
for (StubGroup expected : expectedGroups) {
StubGroup actual = groupMap.get(expected.name());
assertThat(actual.httpServiceTypes()).isEqualTo(expected.httpServiceTypes());
assertThat(actual.clientType()).isEqualTo(expected.clientType());
assertThat(actual.packageNames()).isEqualTo(expected.packageNames());
assertThat(actual.packageClasses()).isEqualTo(expected.packageClasses());
}
}
@ImportHttpServices(group = ECHO_GROUP, types = {EchoA.class, EchoB.class})
private static class ListingConfig {
}
@ImportHttpServices(group = ECHO_GROUP, basePackageClasses = {EchoA.class})
@ImportHttpServices(group = GREETING_GROUP, basePackageClasses = {GreetingA.class})
private static class ScanConfig {
}
@HttpServiceGroups(clientType = ClientType.WEB_CLIENT, groups = {
@ImportHttpServices(group = ECHO_GROUP, types = {EchoA.class}),
@ImportHttpServices(group = GREETING_GROUP, types = {GreetingA.class})
})
private static class ContainerConfig {
}
private static class TestAnnotationHttpServiceRegistrar extends AnnotationHttpServiceRegistrar {
@Override
public void registerHttpServices(GroupRegistry registry, AnnotationMetadata importMetadata) {
super.registerHttpServices(registry, importMetadata);
}
}
private static class TestGroupRegistry implements AbstractHttpServiceRegistrar.GroupRegistry {
private final Map<String, StubGroup> groupMap = new LinkedHashMap<>();
public Map<String, StubGroup> groupMap() {
return this.groupMap;
}
@Override
public GroupSpec forGroup(String name, ClientType clientType) {
return new TestGroupSpec(name, clientType);
}
private class TestGroupSpec implements GroupSpec {
private final String groupName;
private final ClientType clientType;
public TestGroupSpec(String groupName, ClientType clientType) {
this.groupName = groupName;
this.clientType = clientType;
}
@Override
public GroupSpec register(Class<?>... serviceTypes) {
getOrCreateGroup().httpServiceTypes().addAll(Arrays.asList(serviceTypes));
return this;
}
@Override
public GroupSpec detectInBasePackages(Class<?>... packageClasses) {
getOrCreateGroup().packageClasses().addAll(Arrays.asList(packageClasses));
return this;
}
@Override
public GroupSpec detectInBasePackages(String... packageNames) {
getOrCreateGroup().packageNames().addAll(Arrays.asList(packageNames));
return this;
}
private StubGroup getOrCreateGroup() {
return groupMap.computeIfAbsent(this.groupName, name -> new StubGroup(name, this.clientType));
}
}
}
private record StubGroup(
String name, ClientType clientType, Set<Class<?>> httpServiceTypes,
Set<Class<?>> packageClasses, Set<String> packageNames) implements HttpServiceGroup {
StubGroup(String name, ClientType clientType) {
this(name, clientType, new LinkedHashSet<>(), new LinkedHashSet<>(), new LinkedHashSet<>());
}
public static StubGroup ofListing(String name, Class<?>... httpServiceTypes) {
return ofListing(name, ClientType.UNSPECIFIED, httpServiceTypes);
}
public static StubGroup ofListing(String name, ClientType clientType, Class<?>... httpServiceTypes) {
StubGroup group = new StubGroup(name, clientType);
group.httpServiceTypes().addAll(Arrays.asList(httpServiceTypes));
return group;
}
public static StubGroup ofPackageClasses(String name, Class<?>... packageClasses) {
return ofPackageClasses(name, ClientType.UNSPECIFIED, packageClasses);
}
public static StubGroup ofPackageClasses(String name, ClientType clientType, Class<?>... packageClasses) {
StubGroup group = new StubGroup(name, clientType);
group.packageClasses().addAll(Arrays.asList(packageClasses));
return group;
}
}
}

View File

@@ -0,0 +1,194 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* 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
*
* https://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 org.springframework.web.service.registry;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.web.service.registry.HttpServiceGroup.ClientType;
import org.springframework.web.service.registry.echo.EchoA;
import org.springframework.web.service.registry.echo.EchoB;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Unit tests for {@link AbstractHttpServiceRegistrar}.
* @author Rossen Stoyanchev
*/
@SuppressWarnings("unchecked")
public class HttpServiceRegistrarTests {
private static final String ECHO_GROUP = "echo";
private final SimpleBeanDefinitionRegistry beanDefRegistry = new SimpleBeanDefinitionRegistry();
@Test
void basicListing() {
doRegister(registry -> registry.forGroup(ECHO_GROUP).register(EchoA.class, EchoB.class));
assertRegistryBeanDef(new TestGroup(ECHO_GROUP, EchoA.class, EchoB.class));
assertProxyBeanDef(ECHO_GROUP, EchoA.class);
assertProxyBeanDef(ECHO_GROUP, EchoB.class);
assertBeanDefinitionCount(3);
}
@Test
void basicScan() {
doRegister(registry -> registry.forGroup(ECHO_GROUP).detectInBasePackages(EchoA.class));
assertRegistryBeanDef(new TestGroup(ECHO_GROUP, EchoA.class, EchoB.class));
assertProxyBeanDef(ECHO_GROUP, EchoA.class);
assertProxyBeanDef(ECHO_GROUP, EchoB.class);
assertBeanDefinitionCount(3);
}
@Test
void merge() {
doRegister(
registry -> registry.forGroup(ECHO_GROUP).register(EchoA.class),
registry -> registry.forGroup(ECHO_GROUP).register(EchoB.class));
assertRegistryBeanDef(new TestGroup(ECHO_GROUP, EchoA.class, EchoB.class));
assertProxyBeanDef(ECHO_GROUP, EchoA.class);
assertProxyBeanDef(ECHO_GROUP, EchoB.class);
assertBeanDefinitionCount(3);
}
@Test
void mergeWithOverlap() {
doRegister(
registry -> registry.forGroup(ECHO_GROUP).register(EchoA.class),
registry -> registry.forGroup(ECHO_GROUP).register(EchoA.class));
assertRegistryBeanDef(new TestGroup(ECHO_GROUP, EchoA.class));
assertProxyBeanDef(ECHO_GROUP, EchoA.class);
assertBeanDefinitionCount(2);
}
@Test
void mergeWithClientTypeConflict() {
assertThatIllegalArgumentException().isThrownBy(() -> doRegister(
registry -> registry.forGroup(ECHO_GROUP, ClientType.REST_CLIENT).register(EchoA.class),
registry -> registry.forGroup(ECHO_GROUP, ClientType.WEB_CLIENT).register(EchoB.class)));
}
@Test
void defaultClientType() {
doRegister(ClientType.WEB_CLIENT, registry -> registry.forGroup(ECHO_GROUP).register(EchoA.class));
assertRegistryBeanDef(new TestGroup(ECHO_GROUP, ClientType.WEB_CLIENT, EchoA.class));
}
@Test
void noRegistrations() {
doRegister(registry -> {});
assertRegistryBeanDef();
assertBeanDefinitionCount(1);
}
@SuppressWarnings("unchecked")
private void doRegister(Consumer<AbstractHttpServiceRegistrar.GroupRegistry>... registrars) {
doRegister(ClientType.UNSPECIFIED, registrars);
}
@SuppressWarnings("DataFlowIssue")
private void doRegister(ClientType clientType, Consumer<AbstractHttpServiceRegistrar.GroupRegistry>... consumers) {
for (Consumer<AbstractHttpServiceRegistrar.GroupRegistry> consumer : consumers) {
TestRegistrar registrar = new TestRegistrar(consumer, clientType);
registrar.registerBeanDefinitions(null, beanDefRegistry);
}
}
private void assertRegistryBeanDef(HttpServiceGroup... expectedGroups) {
Map<String, HttpServiceGroup> groupMap = groupMap();
assertThat(groupMap.size()).isEqualTo(expectedGroups.length);
for (HttpServiceGroup expected : expectedGroups) {
HttpServiceGroup actual = groupMap.get(expected.name());
assertThat(actual.httpServiceTypes()).isEqualTo(expected.httpServiceTypes());
assertThat(actual.clientType()).isEqualTo(expected.clientType());
}
}
@SuppressWarnings("unchecked")
private Map<String, HttpServiceGroup> groupMap() {
BeanDefinition beanDef = this.beanDefRegistry.getBeanDefinition("httpServiceProxyRegistry");
assertThat(beanDef.getBeanClassName()).isEqualTo(HttpServiceProxyRegistryFactoryBean.class.getName());
ConstructorArgumentValues args = beanDef.getConstructorArgumentValues();
ConstructorArgumentValues.ValueHolder valueHolder = args.getArgumentValue(0, Map.class);
assertThat(valueHolder).isNotNull();
Map<String, HttpServiceGroup> groupMap = (Map<String, HttpServiceGroup>) valueHolder.getValue();
assertThat(groupMap).isNotNull();
return groupMap;
}
private void assertProxyBeanDef(String group, Class<?> httpServiceType) {
String beanName = group + "#" + httpServiceType.getName();
assertThat(this.beanDefRegistry.containsBeanDefinition(beanName)).isTrue();
BeanDefinition beanDef = this.beanDefRegistry.getBeanDefinition(beanName);
assertThat(beanDef.getBeanClassName()).isEqualTo(httpServiceType.getName());
}
private void assertBeanDefinitionCount(int count) {
assertThat(beanDefRegistry.getBeanDefinitionCount()).isEqualTo(count);
}
private static class TestRegistrar extends AbstractHttpServiceRegistrar {
private final Consumer<GroupRegistry> registrar;
TestRegistrar(Consumer<GroupRegistry> registrar, ClientType clientType) {
this.registrar = registrar;
setDefaultClientType(clientType);
setEnvironment(new StandardEnvironment());
setResourceLoader(new PathMatchingResourcePatternResolver());
}
@Override
protected void registerHttpServices(GroupRegistry registry, AnnotationMetadata metadata) {
this.registrar.accept(registry);
}
}
private record TestGroup(String name, Set<Class<?>> httpServiceTypes, ClientType clientType)
implements HttpServiceGroup {
TestGroup(String name, Class<?>... httpServiceTypes) {
this(name, Set.of(httpServiceTypes), ClientType.REST_CLIENT);
}
TestGroup(String name, ClientType clientType, Class<?>... httpServiceTypes) {
this(name, Set.of(httpServiceTypes), clientType);
}
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.web.client.support.echo;
package org.springframework.web.service.registry.echo;
import org.springframework.web.bind.annotation.RequestParam;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.web.client.support.echo;
package org.springframework.web.service.registry.echo;
import org.springframework.web.bind.annotation.RequestParam;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.web.client.support.greeting;
package org.springframework.web.service.registry.greeting;
import org.springframework.web.bind.annotation.RequestParam;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.web.client.support.greeting;
package org.springframework.web.service.registry.greeting;
import org.springframework.web.bind.annotation.RequestParam;