mirror of https://github.com/halo-dev/halo
feat: create reverse proxy for logo when plugin created (#2652)
#### What type of PR is this? /kind improvement /area core /milestone 2.0 #### What this PR does / why we need it: 插件 logo 支持配置外部 URL 或相对于 resources 的路径 Console 端展示插件 logo 时使用的字段需要使用 status.logo 而非 spec.logo #### Which issue(s) this PR fixes: Fixes #2651 #### Special notes for your reviewer: how to test it? 1. 插件配置 logo 为外部 url 例如 https://guqing.xyz/avatar 2. 安装此插件后不会注册 ReverseProxy 规则 3. logo 配置为相对于 resources 的路径例如:/logo.png 4. 安装此插件后可以访问到 /plugins/{your-plugin-name}/assets/logo.png 5. 开发模式启动插件后,修改了 plugin.yaml 中的 spec.logo 则也会更新 ReverseProxy 的 rule /cc @halo-dev/sig-halo #### Does this PR introduce a user-facing change? ```release-note 插件 Logo 支持配置外部 URL 或相对于 resources 的路径 ```pull/2663/head
parent
1078145b18
commit
73df5e4576
|
@ -112,6 +112,8 @@ public class Plugin extends AbstractExtension {
|
|||
private String entry;
|
||||
|
||||
private String stylesheet;
|
||||
|
||||
private String logo;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
|
|
|
@ -4,22 +4,29 @@ import java.io.IOException;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.pf4j.PluginRuntimeException;
|
||||
import org.pf4j.PluginState;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.pf4j.RuntimeMode;
|
||||
import run.halo.app.core.extension.Plugin;
|
||||
import run.halo.app.core.extension.ReverseProxy;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.controller.Reconciler;
|
||||
import run.halo.app.extension.controller.Reconciler.Request;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
import run.halo.app.infra.utils.PathUtils;
|
||||
import run.halo.app.plugin.HaloPluginManager;
|
||||
import run.halo.app.plugin.PluginConst;
|
||||
import run.halo.app.plugin.PluginStartingError;
|
||||
import run.halo.app.plugin.resources.BundleResourceUtils;
|
||||
|
||||
|
@ -51,6 +58,7 @@ public class PluginReconciler implements Reconciler<Request> {
|
|||
}
|
||||
addFinalizerIfNecessary(plugin);
|
||||
reconcilePluginState(plugin.getMetadata().getName());
|
||||
createInitialReverseProxyIfNotPresent(plugin);
|
||||
});
|
||||
return new Result(false, null);
|
||||
}
|
||||
|
@ -78,6 +86,15 @@ public class PluginReconciler implements Reconciler<Request> {
|
|||
}
|
||||
}
|
||||
|
||||
String logo = plugin.getSpec().getLogo();
|
||||
if (PathUtils.isAbsoluteUri(logo)) {
|
||||
pluginStatus.setLogo(logo);
|
||||
} else {
|
||||
String assetsPrefix =
|
||||
PluginConst.assertsRoutePrefix(plugin.getMetadata().getName());
|
||||
pluginStatus.setLogo(PathUtils.combinePath(assetsPrefix, logo));
|
||||
}
|
||||
|
||||
if (!plugin.equals(oldPlugin)) {
|
||||
client.update(plugin);
|
||||
}
|
||||
|
@ -213,4 +230,45 @@ public class PluginReconciler implements Reconciler<Request> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void createInitialReverseProxyIfNotPresent(Plugin plugin) {
|
||||
String pluginName = plugin.getMetadata().getName();
|
||||
String reverseProxyName = initialReverseProxyName(pluginName);
|
||||
ReverseProxy reverseProxy = new ReverseProxy();
|
||||
reverseProxy.setMetadata(new Metadata());
|
||||
reverseProxy.getMetadata().setName(reverseProxyName);
|
||||
// put label to identify this reverse
|
||||
reverseProxy.getMetadata().setLabels(new HashMap<>());
|
||||
reverseProxy.getMetadata().getLabels().put(PluginConst.PLUGIN_NAME_LABEL_NAME, pluginName);
|
||||
|
||||
reverseProxy.setRules(new ArrayList<>());
|
||||
|
||||
String logo = plugin.getSpec().getLogo();
|
||||
if (StringUtils.isNotBlank(logo) && !PathUtils.isAbsoluteUri(logo)) {
|
||||
ReverseProxy.ReverseProxyRule logoRule = new ReverseProxy.ReverseProxyRule(logo,
|
||||
new ReverseProxy.FileReverseProxyProvider(null, logo));
|
||||
reverseProxy.getRules().add(logoRule);
|
||||
}
|
||||
|
||||
client.fetch(ReverseProxy.class, reverseProxyName)
|
||||
.ifPresentOrElse(persisted -> {
|
||||
if (isDevelopmentMode(pluginName)) {
|
||||
reverseProxy.getMetadata()
|
||||
.setVersion(persisted.getMetadata().getVersion());
|
||||
client.update(reverseProxy);
|
||||
}
|
||||
}, () -> client.create(reverseProxy));
|
||||
}
|
||||
|
||||
static String initialReverseProxyName(String pluginName) {
|
||||
return pluginName + "-system-generated-reverse-proxy";
|
||||
}
|
||||
|
||||
private boolean isDevelopmentMode(String name) {
|
||||
PluginWrapper pluginWrapper = haloPluginManager.getPlugin(name);
|
||||
if (pluginWrapper == null) {
|
||||
return false;
|
||||
}
|
||||
return RuntimeMode.DEVELOPMENT.equals(pluginWrapper.getRuntimeMode());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.infra.utils;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
|
@ -12,6 +14,38 @@ import org.apache.commons.lang3.StringUtils;
|
|||
@UtilityClass
|
||||
public class PathUtils {
|
||||
|
||||
/**
|
||||
* Every HTTP URL conforms to the syntax of a generic URI. The URI generic syntax consists of
|
||||
* components organized hierarchically in order of decreasing significance from left to
|
||||
* right:
|
||||
* <pre>
|
||||
* URI = scheme ":" ["//" authority] path ["?" query] ["#" fragment]
|
||||
* </pre>
|
||||
* The authority component consists of subcomponents:
|
||||
* <pre>
|
||||
* authority = [userinfo "@"] host [":" port]
|
||||
* </pre>
|
||||
* Examples of popular schemes include http, https, ftp, mailto, file, data and irc. URI
|
||||
* schemes should be registered with the
|
||||
* <a href="https://en.wikipedia.org/wiki/Internet_Assigned_Numbers_Authority">Internet Assigned Numbers Authority (IANA)</a>, although
|
||||
* non-registered schemes are used in practice.
|
||||
*
|
||||
* @param uriString url or path
|
||||
* @return true if the linkBase is absolute, otherwise false
|
||||
* @see <a href="https://en.wikipedia.org/wiki/URL">URL</a>
|
||||
*/
|
||||
public static boolean isAbsoluteUri(final String uriString) {
|
||||
if (StringUtils.isBlank(uriString)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
URI uri = new URI(uriString);
|
||||
return uri.isAbsolute();
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine paths based on the passed in path segments parameters.
|
||||
*
|
||||
|
|
|
@ -76,7 +76,7 @@ public abstract class BundleResourceUtils {
|
|||
}
|
||||
|
||||
@Nullable
|
||||
private static DefaultResourceLoader getResourceLoader(HaloPluginManager pluginManager,
|
||||
public static DefaultResourceLoader getResourceLoader(HaloPluginManager pluginManager,
|
||||
String pluginName) {
|
||||
Assert.notNull(pluginManager, "Plugin manager must not be null");
|
||||
PluginWrapper plugin = pluginManager.getPlugin(pluginName);
|
||||
|
|
|
@ -4,10 +4,13 @@ import static org.springframework.http.MediaType.ALL;
|
|||
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.http.server.PathContainer;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -22,8 +25,10 @@ import reactor.core.publisher.Mono;
|
|||
import run.halo.app.core.extension.ReverseProxy;
|
||||
import run.halo.app.core.extension.ReverseProxy.FileReverseProxyProvider;
|
||||
import run.halo.app.core.extension.ReverseProxy.ReverseProxyRule;
|
||||
import run.halo.app.infra.exception.NotFoundException;
|
||||
import run.halo.app.infra.utils.PathUtils;
|
||||
import run.halo.app.plugin.PluginApplicationContext;
|
||||
import run.halo.app.plugin.ExtensionContextRegistry;
|
||||
import run.halo.app.plugin.HaloPluginManager;
|
||||
import run.halo.app.plugin.PluginConst;
|
||||
|
||||
/**
|
||||
|
@ -36,39 +41,40 @@ import run.halo.app.plugin.PluginConst;
|
|||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class ReverseProxyRouterFunctionFactory {
|
||||
|
||||
private final HaloPluginManager haloPluginManager;
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
/**
|
||||
* <p>Create {@link RouterFunction} according to the {@link ReverseProxy} custom resource
|
||||
* configuration of the plugin.</p>
|
||||
* <p>Note that: returns {@code Null} if the plugin does not have a {@link ReverseProxy} custom
|
||||
* resource.</p>
|
||||
*
|
||||
* @param applicationContext plugin application context or system application context
|
||||
* @param pluginName plugin name(nullable if system)
|
||||
* @return A reverse proxy RouterFunction handle(nullable)
|
||||
*/
|
||||
@NonNull
|
||||
public Mono<RouterFunction<ServerResponse>> create(ReverseProxy reverseProxy,
|
||||
ApplicationContext applicationContext) {
|
||||
return createReverseProxyRouterFunction(reverseProxy, applicationContext);
|
||||
String pluginName) {
|
||||
return createReverseProxyRouterFunction(reverseProxy, nullSafePluginName(pluginName));
|
||||
}
|
||||
|
||||
private Mono<RouterFunction<ServerResponse>> createReverseProxyRouterFunction(
|
||||
ReverseProxy reverseProxy,
|
||||
ApplicationContext applicationContext) {
|
||||
ReverseProxy reverseProxy, @NonNull String pluginName) {
|
||||
Assert.notNull(reverseProxy, "The reverseProxy must not be null.");
|
||||
Assert.notNull(applicationContext, "The applicationContext must not be null.");
|
||||
final var pluginId = getPluginId(applicationContext);
|
||||
var rules = getReverseProxyRules(reverseProxy);
|
||||
|
||||
return rules.map(rule -> {
|
||||
String routePath = buildRoutePath(pluginId, rule);
|
||||
log.debug("Plugin [{}] registered reverse proxy route path [{}]", pluginId,
|
||||
String routePath = buildRoutePath(pluginName, rule);
|
||||
log.debug("Plugin [{}] registered reverse proxy route path [{}]", pluginName,
|
||||
routePath);
|
||||
return RouterFunctions.route(GET(routePath).and(accept(ALL)),
|
||||
request -> {
|
||||
Resource resource =
|
||||
loadResourceByFileRule(pluginId, applicationContext, rule, request);
|
||||
loadResourceByFileRule(pluginName, rule, request);
|
||||
if (!resource.exists()) {
|
||||
return ServerResponse.notFound().build();
|
||||
}
|
||||
|
@ -78,11 +84,8 @@ public class ReverseProxyRouterFunctionFactory {
|
|||
}).reduce(RouterFunction::and);
|
||||
}
|
||||
|
||||
private String getPluginId(ApplicationContext applicationContext) {
|
||||
if (applicationContext instanceof PluginApplicationContext pluginApplicationContext) {
|
||||
return pluginApplicationContext.getPluginId();
|
||||
}
|
||||
return PluginConst.SYSTEM_PLUGIN_NAME;
|
||||
private String nullSafePluginName(String pluginName) {
|
||||
return pluginName == null ? PluginConst.SYSTEM_PLUGIN_NAME : pluginName;
|
||||
}
|
||||
|
||||
private Flux<ReverseProxyRule> getReverseProxyRules(ReverseProxy reverseProxy) {
|
||||
|
@ -105,15 +108,14 @@ public class ReverseProxyRouterFunctionFactory {
|
|||
* <p>Note that a returned Resource handle does not imply an existing resource; you need to
|
||||
* invoke {@link Resource#exists()} to check for existence</p>
|
||||
*
|
||||
* @param pluginApplicationContext load file from plugin
|
||||
* @param pluginName plugin to load file by name
|
||||
* @param rule reverse proxy rule
|
||||
* @param request client request
|
||||
* @return a Resource handle for the specified resource location by the plugin(never null);
|
||||
*/
|
||||
@NonNull
|
||||
private Resource loadResourceByFileRule(String pluginId,
|
||||
ApplicationContext pluginApplicationContext,
|
||||
ReverseProxyRule rule, ServerRequest request) {
|
||||
private Resource loadResourceByFileRule(String pluginName, ReverseProxyRule rule,
|
||||
ServerRequest request) {
|
||||
Assert.notNull(rule.file(), "File rule must not be null.");
|
||||
FileReverseProxyProvider file = rule.file();
|
||||
String directory = file.directory();
|
||||
|
@ -124,14 +126,30 @@ public class ReverseProxyRouterFunctionFactory {
|
|||
if (StringUtils.isNotBlank(configuredFilename)) {
|
||||
filename = configuredFilename;
|
||||
} else {
|
||||
String routePath = buildRoutePath(pluginId, rule);
|
||||
String routePath = buildRoutePath(pluginName, rule);
|
||||
PathContainer pathContainer = PathPatternParser.defaultInstance.parse(routePath)
|
||||
.extractPathWithinPattern(PathContainer.parsePath(request.path()));
|
||||
filename = pathContainer.value();
|
||||
}
|
||||
|
||||
String filePath = PathUtils.combinePath(directory, filename);
|
||||
return pluginApplicationContext.getResource(filePath);
|
||||
return getResourceLoader(pluginName).getResource(filePath);
|
||||
}
|
||||
|
||||
private ResourceLoader getResourceLoader(String pluginName) {
|
||||
ExtensionContextRegistry registry = ExtensionContextRegistry.getInstance();
|
||||
if (registry.containsContext(pluginName)) {
|
||||
return registry.getByPluginId(pluginName);
|
||||
}
|
||||
if (PluginConst.SYSTEM_PLUGIN_NAME.equals(pluginName)) {
|
||||
return applicationContext;
|
||||
}
|
||||
DefaultResourceLoader resourceLoader =
|
||||
BundleResourceUtils.getResourceLoader(haloPluginManager, pluginName);
|
||||
if (resourceLoader == null) {
|
||||
throw new NotFoundException("Plugin [" + pluginName + "] not found.");
|
||||
}
|
||||
return resourceLoader;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,8 +12,6 @@ import org.springframework.web.reactive.function.server.RouterFunction;
|
|||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.ReverseProxy;
|
||||
import run.halo.app.plugin.ExtensionContextRegistry;
|
||||
import run.halo.app.plugin.PluginApplicationContext;
|
||||
|
||||
/**
|
||||
* A registry for {@link RouterFunction} of plugin.
|
||||
|
@ -48,11 +46,7 @@ public class ReverseProxyRouterFunctionRegistry {
|
|||
long stamp = lock.writeLock();
|
||||
try {
|
||||
pluginIdReverseProxyMap.put(pluginId, proxyName);
|
||||
|
||||
// Obtain plugin application context
|
||||
PluginApplicationContext pluginApplicationContext =
|
||||
ExtensionContextRegistry.getInstance().getByPluginId(pluginId);
|
||||
return reverseProxyRouterFunctionFactory.create(reverseProxy, pluginApplicationContext)
|
||||
return reverseProxyRouterFunctionFactory.create(reverseProxy, pluginId)
|
||||
.map(routerFunction -> {
|
||||
proxyNameRouterFunctionRegistry.put(proxyName, routerFunction);
|
||||
return routerFunction;
|
||||
|
|
|
@ -4,13 +4,18 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static run.halo.app.core.extension.reconciler.PluginReconciler.initialReverseProxyName;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -21,8 +26,12 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
|||
import org.pf4j.PluginRuntimeException;
|
||||
import org.pf4j.PluginState;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.pf4j.RuntimeMode;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import run.halo.app.core.extension.Plugin;
|
||||
import run.halo.app.core.extension.ReverseProxy;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.controller.Reconciler;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
import run.halo.app.plugin.HaloPluginManager;
|
||||
|
@ -52,8 +61,8 @@ class PluginReconcilerTest {
|
|||
void setUp() {
|
||||
pluginReconciler = new PluginReconciler(extensionClient, haloPluginManager);
|
||||
|
||||
when(haloPluginManager.getPlugin(any())).thenReturn(pluginWrapper);
|
||||
when(haloPluginManager.getUnresolvedPlugins()).thenReturn(List.of());
|
||||
lenient().when(haloPluginManager.getPlugin(any())).thenReturn(pluginWrapper);
|
||||
lenient().when(haloPluginManager.getUnresolvedPlugins()).thenReturn(List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -66,7 +75,7 @@ class PluginReconcilerTest {
|
|||
when(pluginWrapper.getPluginState()).thenReturn(PluginState.STOPPED);
|
||||
|
||||
ArgumentCaptor<Plugin> pluginCaptor = doReconcileWithoutRequeue();
|
||||
verify(extensionClient, times(2)).update(any());
|
||||
verify(extensionClient, times(3)).update(isA(Plugin.class));
|
||||
|
||||
Plugin updateArgs = pluginCaptor.getValue();
|
||||
assertThat(updateArgs).isNotNull();
|
||||
|
@ -118,7 +127,7 @@ class PluginReconcilerTest {
|
|||
when(pluginWrapper.getPluginState()).thenReturn(PluginState.STARTED);
|
||||
|
||||
ArgumentCaptor<Plugin> pluginCaptor = doReconcileWithoutRequeue();
|
||||
verify(extensionClient, times(2)).update(any());
|
||||
verify(extensionClient, times(3)).update(any(Plugin.class));
|
||||
|
||||
Plugin updateArgs = pluginCaptor.getValue();
|
||||
assertThat(updateArgs).isNotNull();
|
||||
|
@ -153,7 +162,7 @@ class PluginReconcilerTest {
|
|||
when(pluginWrapper.getPluginState()).thenReturn(PluginState.STARTED);
|
||||
|
||||
ArgumentCaptor<Plugin> pluginCaptor = doReconcileWithoutRequeue();
|
||||
verify(extensionClient, times(3)).update(any());
|
||||
verify(extensionClient, times(3)).update(any(Plugin.class));
|
||||
|
||||
Plugin updateArgs = pluginCaptor.getValue();
|
||||
assertThat(updateArgs).isNotNull();
|
||||
|
@ -192,6 +201,80 @@ class PluginReconcilerTest {
|
|||
.hasMessage("error message");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createInitialReverseProxyWhenNotExistAndLogoIsPath() throws JSONException {
|
||||
Plugin plugin = need2ReconcileForStopState();
|
||||
String reverseProxyName = initialReverseProxyName(plugin.getMetadata().getName());
|
||||
when(extensionClient.fetch(eq(ReverseProxy.class), eq(reverseProxyName)))
|
||||
.thenReturn(Optional.empty());
|
||||
|
||||
plugin.getSpec().setLogo("/logo.png");
|
||||
pluginReconciler.createInitialReverseProxyIfNotPresent(plugin);
|
||||
ArgumentCaptor<ReverseProxy> captor = ArgumentCaptor.forClass(ReverseProxy.class);
|
||||
verify(extensionClient, times(1)).create(captor.capture());
|
||||
ReverseProxy value = captor.getValue();
|
||||
JSONAssert.assertEquals("""
|
||||
{
|
||||
"rules": [
|
||||
{
|
||||
"path": "/logo.png",
|
||||
"file": {
|
||||
"filename": "/logo.png"
|
||||
}
|
||||
}
|
||||
],
|
||||
"apiVersion": "plugin.halo.run/v1alpha1",
|
||||
"kind": "ReverseProxy",
|
||||
"metadata": {
|
||||
"name": "apples-system-generated-reverse-proxy",
|
||||
"labels": {
|
||||
"plugin.halo.run/plugin-name": "apples"
|
||||
}
|
||||
}
|
||||
}
|
||||
""",
|
||||
JsonUtils.objectToJson(value),
|
||||
true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createInitialReverseProxyWhenNotExistAndLogoIsAbsolute() {
|
||||
Plugin plugin = need2ReconcileForStopState();
|
||||
String reverseProxyName = initialReverseProxyName(plugin.getMetadata().getName());
|
||||
when(extensionClient.fetch(eq(ReverseProxy.class), eq(reverseProxyName)))
|
||||
.thenReturn(Optional.empty());
|
||||
|
||||
plugin.getSpec().setLogo("http://example.com/logo");
|
||||
pluginReconciler.createInitialReverseProxyIfNotPresent(plugin);
|
||||
ArgumentCaptor<ReverseProxy> captor = ArgumentCaptor.forClass(ReverseProxy.class);
|
||||
verify(extensionClient, times(1)).create(captor.capture());
|
||||
ReverseProxy value = captor.getValue();
|
||||
assertThat(value.getRules()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createInitialReverseProxyWhenExist() {
|
||||
Plugin plugin = need2ReconcileForStopState();
|
||||
plugin.getSpec().setLogo("/logo.png");
|
||||
|
||||
String reverseProxyName = initialReverseProxyName(plugin.getMetadata().getName());
|
||||
ReverseProxy reverseProxy = new ReverseProxy();
|
||||
reverseProxy.setMetadata(new Metadata());
|
||||
reverseProxy.getMetadata().setName(reverseProxyName);
|
||||
reverseProxy.setRules(new ArrayList<>());
|
||||
|
||||
when(extensionClient.fetch(eq(ReverseProxy.class), eq(reverseProxyName)))
|
||||
.thenReturn(Optional.of(reverseProxy));
|
||||
when(pluginWrapper.getRuntimeMode()).thenReturn(RuntimeMode.DEPLOYMENT);
|
||||
|
||||
pluginReconciler.createInitialReverseProxyIfNotPresent(plugin);
|
||||
verify(extensionClient, times(0)).update(any());
|
||||
|
||||
when(pluginWrapper.getRuntimeMode()).thenReturn(RuntimeMode.DEVELOPMENT);
|
||||
pluginReconciler.createInitialReverseProxyIfNotPresent(plugin);
|
||||
verify(extensionClient, times(1)).update(any());
|
||||
}
|
||||
|
||||
private ArgumentCaptor<Plugin> doReconcileNeedRequeue() {
|
||||
ArgumentCaptor<Plugin> pluginCaptor = ArgumentCaptor.forClass(Plugin.class);
|
||||
doNothing().when(extensionClient).update(pluginCaptor.capture());
|
||||
|
|
|
@ -59,4 +59,40 @@ class PathUtilsTest {
|
|||
assertThat(PathUtils.simplifyPathPattern("/archives/{year:\\d{4}}/page/{page:\\d+}"))
|
||||
.isEqualTo("/archives/{year}/page/{page}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAbsoluteUri() {
|
||||
String[] absoluteUris = new String[] {
|
||||
"ftp://ftp.is.co.za/rfc/rfc1808.txt",
|
||||
"http://www.ietf.org/rfc/rfc2396.txt",
|
||||
"ldap://[2001:db8::7]/c=GB?objectClass?one",
|
||||
"mailto:John.Doe@example.com",
|
||||
"news:comp.infosystems.www.servers.unix",
|
||||
"tel:+1-816-555-1212",
|
||||
"telnet://192.0.2.16:80/",
|
||||
"urn:oasis:names:specification:docbook:dtd:xml:4.1.2",
|
||||
"data:text/vnd-example+xyz;foo=bar;base64,R0lGODdh",
|
||||
"irc://irc.example.com:6667/#some-channel",
|
||||
"ircs://irc.example.com:6667/#some-channel",
|
||||
"irc6://irc.example.com:6667/#some-channel"
|
||||
};
|
||||
for (String uri : absoluteUris) {
|
||||
assertThat(PathUtils.isAbsoluteUri(uri)).isTrue();
|
||||
}
|
||||
|
||||
String[] paths = new String[] {
|
||||
"//example.com/path/resource.txt",
|
||||
"/path/resource.txt",
|
||||
"path/resource.txt",
|
||||
"../resource.txt",
|
||||
"./resource.txt",
|
||||
"resource.txt",
|
||||
"#fragment",
|
||||
"",
|
||||
null
|
||||
};
|
||||
for (String path : paths) {
|
||||
assertThat(PathUtils.isAbsoluteUri(path)).isFalse();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +1,17 @@
|
|||
package run.halo.app.plugin.resources;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.core.extension.ReverseProxy;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.plugin.PluginApplicationContext;
|
||||
import run.halo.app.plugin.HaloPluginManager;
|
||||
import run.halo.app.plugin.PluginConst;
|
||||
|
||||
/**
|
||||
|
@ -25,22 +24,18 @@ import run.halo.app.plugin.PluginConst;
|
|||
class ReverseProxyRouterFunctionFactoryTest {
|
||||
|
||||
@Mock
|
||||
private PluginApplicationContext pluginApplicationContext;
|
||||
private HaloPluginManager haloPluginManager;
|
||||
|
||||
@Mock
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@InjectMocks
|
||||
private ReverseProxyRouterFunctionFactory reverseProxyRouterFunctionFactory;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
reverseProxyRouterFunctionFactory =
|
||||
new ReverseProxyRouterFunctionFactory();
|
||||
|
||||
when(pluginApplicationContext.getPluginId()).thenReturn("fakeA");
|
||||
}
|
||||
|
||||
@Test
|
||||
void create() {
|
||||
var routerFunction =
|
||||
reverseProxyRouterFunctionFactory.create(mockReverseProxy(), pluginApplicationContext);
|
||||
reverseProxyRouterFunctionFactory.create(mockReverseProxy(), "fakeA");
|
||||
StepVerifier.create(routerFunction)
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
|
|
Loading…
Reference in New Issue