mirror of https://github.com/halo-dev/halo
feat: add reset config API for theme and plugin (#2964)
#### What type of PR is this? /kind feature /kind api-change /area core #### What this PR does / why we need it: 为主题和插件提供重置设置项 API 此 PR 会重新读取配置对应的 Setting 资源,从其中读取默认值后更新到现有的 ConfigMap 中替换其 data see #2789 for more details #### Which issue(s) this PR fixes: Fixes #2789 #### Special notes for your reviewer: how to test it? 1. 在主题设置或插件设置配置一些设置项后保存 2. 执行重置配置 3. 配置恢复为了 Setting 中指定的默认值 /cc @halo-dev/sig-halo #### Does this PR introduce a user-facing change? ```release-note 为主题和插件提供重置设置项 API ```pull/3003/head
parent
efc940df99
commit
27775c9ac9
|
@ -25,11 +25,13 @@ import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||||
|
import org.springframework.dao.OptimisticLockingFailureException;
|
||||||
import org.springframework.data.domain.Sort;
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.codec.multipart.FilePart;
|
import org.springframework.http.codec.multipart.FilePart;
|
||||||
|
@ -49,7 +51,10 @@ import reactor.core.publisher.Mono;
|
||||||
import reactor.core.scheduler.Schedulers;
|
import reactor.core.scheduler.Schedulers;
|
||||||
import reactor.util.retry.Retry;
|
import reactor.util.retry.Retry;
|
||||||
import run.halo.app.core.extension.Plugin;
|
import run.halo.app.core.extension.Plugin;
|
||||||
|
import run.halo.app.core.extension.Setting;
|
||||||
|
import run.halo.app.core.extension.theme.SettingUtils;
|
||||||
import run.halo.app.extension.Comparators;
|
import run.halo.app.extension.Comparators;
|
||||||
|
import run.halo.app.extension.ConfigMap;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.extension.router.IListRequest.QueryListRequest;
|
import run.halo.app.extension.router.IListRequest.QueryListRequest;
|
||||||
import run.halo.app.infra.utils.FileUtils;
|
import run.halo.app.infra.utils.FileUtils;
|
||||||
|
@ -95,6 +100,19 @@ public class PluginEndpoint implements CustomEndpoint {
|
||||||
.content(contentBuilder().mediaType(MediaType.MULTIPART_FORM_DATA_VALUE)
|
.content(contentBuilder().mediaType(MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||||
.schema(schemaBuilder().implementation(InstallRequest.class))))
|
.schema(schemaBuilder().implementation(InstallRequest.class))))
|
||||||
)
|
)
|
||||||
|
.PUT("plugins/{name}/reset-config", this::resetSettingConfig,
|
||||||
|
builder -> builder.operationId("ResetPluginConfig")
|
||||||
|
.description("Reset the configMap of plugin setting.")
|
||||||
|
.tag(tag)
|
||||||
|
.parameter(parameterBuilder()
|
||||||
|
.name("name")
|
||||||
|
.in(ParameterIn.PATH)
|
||||||
|
.required(true)
|
||||||
|
.implementation(String.class)
|
||||||
|
)
|
||||||
|
.response(responseBuilder()
|
||||||
|
.implementation(ConfigMap.class))
|
||||||
|
)
|
||||||
.GET("plugins", this::list, builder -> {
|
.GET("plugins", this::list, builder -> {
|
||||||
builder.operationId("ListPlugins")
|
builder.operationId("ListPlugins")
|
||||||
.tag(tag)
|
.tag(tag)
|
||||||
|
@ -105,6 +123,32 @@ public class PluginEndpoint implements CustomEndpoint {
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Mono<ServerResponse> resetSettingConfig(ServerRequest request) {
|
||||||
|
String name = request.pathVariable("name");
|
||||||
|
return client.fetch(Plugin.class, name)
|
||||||
|
.filter(plugin -> StringUtils.hasText(plugin.getSpec().getSettingName()))
|
||||||
|
.flatMap(plugin -> {
|
||||||
|
String configMapName = plugin.getSpec().getConfigMapName();
|
||||||
|
String settingName = plugin.getSpec().getSettingName();
|
||||||
|
return client.fetch(Setting.class, settingName)
|
||||||
|
.map(SettingUtils::settingDefinedDefaultValueMap)
|
||||||
|
.flatMap(data -> updateConfigMapData(configMapName, data));
|
||||||
|
})
|
||||||
|
.flatMap(configMap -> ServerResponse.ok()
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.bodyValue(configMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mono<ConfigMap> updateConfigMapData(String configMapName, Map<String, String> data) {
|
||||||
|
return client.fetch(ConfigMap.class, configMapName)
|
||||||
|
.flatMap(configMap -> {
|
||||||
|
configMap.setData(data);
|
||||||
|
return client.update(configMap);
|
||||||
|
})
|
||||||
|
.retryWhen(Retry.fixedDelay(10, Duration.ofMillis(100))
|
||||||
|
.filter(t -> t instanceof OptimisticLockingFailureException));
|
||||||
|
}
|
||||||
|
|
||||||
private Mono<ServerResponse> upgrade(ServerRequest request) {
|
private Mono<ServerResponse> upgrade(ServerRequest request) {
|
||||||
var pluginNameInPath = request.pathVariable("name");
|
var pluginNameInPath = request.pathVariable("name");
|
||||||
var tempDirRef = new AtomicReference<Path>();
|
var tempDirRef = new AtomicReference<Path>();
|
||||||
|
|
|
@ -1,19 +1,15 @@
|
||||||
package run.halo.app.core.extension.reconciler;
|
package run.halo.app.core.extension.reconciler;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.FileSystemUtils;
|
import org.springframework.util.FileSystemUtils;
|
||||||
import run.halo.app.core.extension.Setting;
|
import run.halo.app.core.extension.Setting;
|
||||||
import run.halo.app.core.extension.Theme;
|
import run.halo.app.core.extension.Theme;
|
||||||
|
import run.halo.app.core.extension.theme.SettingUtils;
|
||||||
import run.halo.app.extension.ConfigMap;
|
import run.halo.app.extension.ConfigMap;
|
||||||
import run.halo.app.extension.ExtensionClient;
|
import run.halo.app.extension.ExtensionClient;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
|
@ -106,7 +102,7 @@ public class ThemeReconciler implements Reconciler<Request> {
|
||||||
|
|
||||||
client.fetch(Setting.class, theme.getSpec().getSettingName())
|
client.fetch(Setting.class, theme.getSpec().getSettingName())
|
||||||
.ifPresent(setting -> {
|
.ifPresent(setting -> {
|
||||||
Map<String, String> data = settingDefinedDefaultValueMap(setting);
|
var data = SettingUtils.settingDefinedDefaultValueMap(setting);
|
||||||
if (CollectionUtils.isEmpty(data)) {
|
if (CollectionUtils.isEmpty(data)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -118,31 +114,6 @@ public class ThemeReconciler implements Reconciler<Request> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> settingDefinedDefaultValueMap(Setting setting) {
|
|
||||||
final String defaultValueField = "value";
|
|
||||||
final String nameField = "name";
|
|
||||||
List<Setting.SettingForm> forms = setting.getSpec().getForms();
|
|
||||||
if (CollectionUtils.isEmpty(forms)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Map<String, String> data = new LinkedHashMap<>();
|
|
||||||
for (Setting.SettingForm form : forms) {
|
|
||||||
String group = form.getGroup();
|
|
||||||
Map<String, JsonNode> groupValue = form.getFormSchema().stream()
|
|
||||||
.map(o -> JsonUtils.DEFAULT_JSON_MAPPER.convertValue(o, JsonNode.class))
|
|
||||||
.filter(jsonNode -> jsonNode.isObject() && jsonNode.has(nameField)
|
|
||||||
&& jsonNode.has(defaultValueField))
|
|
||||||
.map(jsonNode -> {
|
|
||||||
String name = jsonNode.findValue(nameField).asText();
|
|
||||||
JsonNode value = jsonNode.findValue(defaultValueField);
|
|
||||||
return Map.entry(name, value);
|
|
||||||
})
|
|
||||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
|
||||||
data.put(group, JsonUtils.objectToJson(groupValue));
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reconcileThemeDeletion(Theme theme) {
|
private void reconcileThemeDeletion(Theme theme) {
|
||||||
deleteThemeFiles(theme);
|
deleteThemeFiles(theme);
|
||||||
// delete theme setting form
|
// delete theme setting form
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
package run.halo.app.core.extension.theme;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
import run.halo.app.core.extension.Setting;
|
||||||
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class SettingUtils {
|
||||||
|
private static final String VALUE_FIELD = "value";
|
||||||
|
private static final String NAME_FIELD = "name";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read setting default value from {@link Setting} forms.
|
||||||
|
*
|
||||||
|
* @param setting {@link Setting} extension
|
||||||
|
* @return a map of setting default value
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static Map<String, String> settingDefinedDefaultValueMap(Setting setting) {
|
||||||
|
List<Setting.SettingForm> forms = setting.getSpec().getForms();
|
||||||
|
if (CollectionUtils.isEmpty(forms)) {
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
Map<String, String> data = new LinkedHashMap<>();
|
||||||
|
for (Setting.SettingForm form : forms) {
|
||||||
|
String group = form.getGroup();
|
||||||
|
Map<String, JsonNode> groupValue = form.getFormSchema().stream()
|
||||||
|
.map(o -> JsonUtils.DEFAULT_JSON_MAPPER.convertValue(o, JsonNode.class))
|
||||||
|
.filter(jsonNode -> jsonNode.isObject() && jsonNode.has(NAME_FIELD)
|
||||||
|
&& jsonNode.has(VALUE_FIELD))
|
||||||
|
.map(jsonNode -> {
|
||||||
|
String name = jsonNode.get(NAME_FIELD).asText();
|
||||||
|
JsonNode value = jsonNode.get(VALUE_FIELD);
|
||||||
|
return Map.entry(name, value);
|
||||||
|
})
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||||
|
data.put(group, JsonUtils.objectToJson(groupValue));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ import reactor.core.Exceptions;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.Theme;
|
import run.halo.app.core.extension.Theme;
|
||||||
import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
||||||
|
import run.halo.app.extension.ConfigMap;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.extension.router.IListRequest;
|
import run.halo.app.extension.router.IListRequest;
|
||||||
|
@ -105,6 +106,19 @@ public class ThemeEndpoint implements CustomEndpoint {
|
||||||
.response(responseBuilder()
|
.response(responseBuilder()
|
||||||
.implementation(Theme.class))
|
.implementation(Theme.class))
|
||||||
)
|
)
|
||||||
|
.PUT("themes/{name}/reset-config", this::resetSettingConfig,
|
||||||
|
builder -> builder.operationId("ResetThemeConfig")
|
||||||
|
.description("Reset the configMap of theme setting.")
|
||||||
|
.tag(tag)
|
||||||
|
.parameter(parameterBuilder()
|
||||||
|
.name("name")
|
||||||
|
.in(ParameterIn.PATH)
|
||||||
|
.required(true)
|
||||||
|
.implementation(String.class)
|
||||||
|
)
|
||||||
|
.response(responseBuilder()
|
||||||
|
.implementation(ConfigMap.class))
|
||||||
|
)
|
||||||
.GET("themes", this::listThemes,
|
.GET("themes", this::listThemes,
|
||||||
builder -> {
|
builder -> {
|
||||||
builder.operationId("ListThemes")
|
builder.operationId("ListThemes")
|
||||||
|
@ -222,6 +236,14 @@ public class ThemeEndpoint implements CustomEndpoint {
|
||||||
.bodyValue(theme));
|
.bodyValue(theme));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Mono<ServerResponse> resetSettingConfig(ServerRequest request) {
|
||||||
|
String name = request.pathVariable("name");
|
||||||
|
return themeService.resetSettingConfig(name)
|
||||||
|
.flatMap(theme -> ServerResponse.ok()
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.bodyValue(theme));
|
||||||
|
}
|
||||||
|
|
||||||
public record InstallRequest(
|
public record InstallRequest(
|
||||||
@Schema(required = true, description = "Theme zip file.") FilePart file) {
|
@Schema(required = true, description = "Theme zip file.") FilePart file) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package run.halo.app.core.extension.theme;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.Theme;
|
import run.halo.app.core.extension.Theme;
|
||||||
|
import run.halo.app.extension.ConfigMap;
|
||||||
|
|
||||||
public interface ThemeService {
|
public interface ThemeService {
|
||||||
|
|
||||||
|
@ -11,6 +12,8 @@ public interface ThemeService {
|
||||||
Mono<Theme> upgrade(String themeName, InputStream is);
|
Mono<Theme> upgrade(String themeName, InputStream is);
|
||||||
|
|
||||||
Mono<Theme> reloadTheme(String name);
|
Mono<Theme> reloadTheme(String name);
|
||||||
|
|
||||||
|
Mono<ConfigMap> resetSettingConfig(String name);
|
||||||
// TODO Migrate other useful methods in ThemeEndpoint in the future.
|
// TODO Migrate other useful methods in ThemeEndpoint in the future.
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
@ -18,6 +19,7 @@ import java.util.zip.ZipInputStream;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
|
import org.springframework.dao.OptimisticLockingFailureException;
|
||||||
import org.springframework.retry.RetryException;
|
import org.springframework.retry.RetryException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
@ -206,6 +208,29 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ConfigMap> resetSettingConfig(String name) {
|
||||||
|
return client.fetch(Theme.class, name)
|
||||||
|
.filter(theme -> StringUtils.isNotBlank(theme.getSpec().getSettingName()))
|
||||||
|
.flatMap(theme -> {
|
||||||
|
String configMapName = theme.getSpec().getConfigMapName();
|
||||||
|
String settingName = theme.getSpec().getSettingName();
|
||||||
|
return client.fetch(Setting.class, settingName)
|
||||||
|
.map(SettingUtils::settingDefinedDefaultValueMap)
|
||||||
|
.flatMap(data -> updateConfigMapData(configMapName, data));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mono<ConfigMap> updateConfigMapData(String configMapName, Map<String, String> data) {
|
||||||
|
return client.fetch(ConfigMap.class, configMapName)
|
||||||
|
.flatMap(configMap -> {
|
||||||
|
configMap.setData(data);
|
||||||
|
return client.update(configMap);
|
||||||
|
})
|
||||||
|
.retryWhen(Retry.fixedDelay(10, Duration.ofMillis(100))
|
||||||
|
.filter(t -> t instanceof OptimisticLockingFailureException));
|
||||||
|
}
|
||||||
|
|
||||||
private Mono<Void> waitForSettingDeleted(String settingName) {
|
private Mono<Void> waitForSettingDeleted(String settingName) {
|
||||||
return client.fetch(Setting.class, settingName)
|
return client.fetch(Setting.class, settingName)
|
||||||
.flatMap(setting -> client.delete(setting)
|
.flatMap(setting -> client.delete(setting)
|
||||||
|
|
|
@ -15,6 +15,9 @@ rules:
|
||||||
- apiGroups: [ "plugin.halo.run" ]
|
- apiGroups: [ "plugin.halo.run" ]
|
||||||
resources: [ "plugins" ]
|
resources: [ "plugins" ]
|
||||||
verbs: [ "create", "patch", "update", "delete", "deletecollection" ]
|
verbs: [ "create", "patch", "update", "delete", "deletecollection" ]
|
||||||
|
- apiGroups: [ "api.console.halo.run" ]
|
||||||
|
resources: [ "plugins/upgrade", "plugins/resetconfig" ]
|
||||||
|
verbs: [ "*" ]
|
||||||
- nonResourceURLs: [ "/apis/api.console.halo.run/v1alpha1/plugins/*" ]
|
- nonResourceURLs: [ "/apis/api.console.halo.run/v1alpha1/plugins/*" ]
|
||||||
verbs: [ "create" ]
|
verbs: [ "create" ]
|
||||||
---
|
---
|
||||||
|
|
|
@ -15,7 +15,7 @@ rules:
|
||||||
resources: [ "themes" ]
|
resources: [ "themes" ]
|
||||||
verbs: [ "*" ]
|
verbs: [ "*" ]
|
||||||
- apiGroups: [ "api.console.halo.run" ]
|
- apiGroups: [ "api.console.halo.run" ]
|
||||||
resources: [ "themes", "themes/reload" ]
|
resources: [ "themes", "themes/reload", "themes/resetconfig" ]
|
||||||
verbs: [ "*" ]
|
verbs: [ "*" ]
|
||||||
- nonResourceURLs: [ "/apis/api.console.halo.run/themes/install" ]
|
- nonResourceURLs: [ "/apis/api.console.halo.run/themes/install" ]
|
||||||
verbs: [ "create" ]
|
verbs: [ "create" ]
|
||||||
|
|
|
@ -174,21 +174,6 @@ class ThemeReconcilerTest {
|
||||||
true);
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void settingDefinedDefaultValueMap() throws JSONException {
|
|
||||||
Setting setting = getFakeSetting();
|
|
||||||
when(haloProperties.getWorkDir()).thenReturn(tempDirectory);
|
|
||||||
Map<String, String> map = new ThemeReconciler(extensionClient, haloProperties)
|
|
||||||
.settingDefinedDefaultValueMap(setting);
|
|
||||||
JSONAssert.assertEquals("""
|
|
||||||
{
|
|
||||||
"sns": "{\\"email\\":\\"example@exmple.com\\"}"
|
|
||||||
}
|
|
||||||
""",
|
|
||||||
JsonUtils.objectToJson(map),
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Setting getFakeSetting() {
|
private static Setting getFakeSetting() {
|
||||||
String settingJson = """
|
String settingJson = """
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
package run.halo.app.core.extension.theme;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.skyscreamer.jsonassert.JSONAssert;
|
||||||
|
import run.halo.app.core.extension.Setting;
|
||||||
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link SettingUtils}.
|
||||||
|
*
|
||||||
|
* @author guqing
|
||||||
|
* @since 2.0.1
|
||||||
|
*/
|
||||||
|
class SettingUtilsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void settingDefinedDefaultValueMap() throws JSONException {
|
||||||
|
Setting setting = getFakeSetting();
|
||||||
|
var map = SettingUtils.settingDefinedDefaultValueMap(setting);
|
||||||
|
JSONAssert.assertEquals("""
|
||||||
|
{
|
||||||
|
"sns": "{\\"email\\":\\"example@exmple.com\\"}"
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
JsonUtils.objectToJson(map),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Setting getFakeSetting() {
|
||||||
|
String settingJson = """
|
||||||
|
{
|
||||||
|
"apiVersion": "v1alpha1",
|
||||||
|
"kind": "Setting",
|
||||||
|
"metadata": {
|
||||||
|
"name": "theme-default-setting"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"forms": [{
|
||||||
|
"formSchema": [
|
||||||
|
{
|
||||||
|
"$el": "h1",
|
||||||
|
"children": "Register"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$formkit": "text",
|
||||||
|
"label": "Email",
|
||||||
|
"name": "email",
|
||||||
|
"value": "example@exmple.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$formkit": "password",
|
||||||
|
"label": "Password",
|
||||||
|
"name": "password",
|
||||||
|
"validation": "required|length:5,16",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"group": "sns",
|
||||||
|
"label": "社交资料"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
return JsonUtils.jsonToObject(settingJson, Setting.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -170,4 +170,13 @@ class ThemeEndpointTest {
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().isOk();
|
.expectStatus().isOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resetSettingConfig() {
|
||||||
|
when(themeService.resetSettingConfig(any())).thenReturn(Mono.empty());
|
||||||
|
webTestClient.put()
|
||||||
|
.uri("/themes/fake/reset-config")
|
||||||
|
.exchange()
|
||||||
|
.expectStatus().isOk();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package run.halo.app.core.extension.theme;
|
package run.halo.app.core.extension.theme;
|
||||||
|
|
||||||
import static java.nio.file.Files.createTempDirectory;
|
import static java.nio.file.Files.createTempDirectory;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
@ -17,6 +18,7 @@ import java.net.URISyntaxException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
@ -35,6 +37,7 @@ import reactor.core.publisher.Mono;
|
||||||
import reactor.test.StepVerifier;
|
import reactor.test.StepVerifier;
|
||||||
import run.halo.app.core.extension.Setting;
|
import run.halo.app.core.extension.Setting;
|
||||||
import run.halo.app.core.extension.Theme;
|
import run.halo.app.core.extension.Theme;
|
||||||
|
import run.halo.app.extension.ConfigMap;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.extension.Unstructured;
|
import run.halo.app.extension.Unstructured;
|
||||||
|
@ -375,4 +378,69 @@ class ThemeServiceImplTest {
|
||||||
})
|
})
|
||||||
.verifyComplete();
|
.verifyComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resetSettingConfig() {
|
||||||
|
Theme theme = new Theme();
|
||||||
|
theme.setMetadata(new Metadata());
|
||||||
|
theme.getMetadata().setName("fake-theme");
|
||||||
|
theme.setSpec(new Theme.ThemeSpec());
|
||||||
|
theme.getSpec().setSettingName("fake-setting");
|
||||||
|
theme.getSpec().setConfigMapName("fake-config");
|
||||||
|
theme.getSpec().setDisplayName("Hello");
|
||||||
|
when(client.fetch(Theme.class, "fake-theme"))
|
||||||
|
.thenReturn(Mono.just(theme));
|
||||||
|
|
||||||
|
Setting setting = new Setting();
|
||||||
|
setting.setMetadata(new Metadata());
|
||||||
|
setting.getMetadata().setName("fake-setting");
|
||||||
|
setting.setSpec(new Setting.SettingSpec());
|
||||||
|
var formSchemaItem = Map.of("name", "email", "value", "example@exmple.com");
|
||||||
|
Setting.SettingForm settingForm = new Setting.SettingForm();
|
||||||
|
settingForm.setGroup("basic");
|
||||||
|
settingForm.setFormSchema(List.of(formSchemaItem));
|
||||||
|
setting.getSpec().setForms(List.of(settingForm));
|
||||||
|
when(client.fetch(eq(Setting.class), eq("fake-setting")))
|
||||||
|
.thenReturn(Mono.just(setting));
|
||||||
|
|
||||||
|
ConfigMap configMap = new ConfigMap();
|
||||||
|
configMap.setMetadata(new Metadata());
|
||||||
|
configMap.getMetadata().setName("fake-config");
|
||||||
|
when(client.fetch(eq(ConfigMap.class), eq("fake-config")))
|
||||||
|
.thenReturn(Mono.just(configMap));
|
||||||
|
|
||||||
|
when(client.update(any(ConfigMap.class)))
|
||||||
|
.thenAnswer((Answer<Mono<ConfigMap>>) invocation -> {
|
||||||
|
ConfigMap argument = invocation.getArgument(0);
|
||||||
|
JSONAssert.assertEquals("""
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"basic": "{\\"email\\":\\"example@exmple.com\\"}"
|
||||||
|
},
|
||||||
|
"apiVersion": "v1alpha1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": {
|
||||||
|
"name": "fake-config"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
JsonUtils.objectToJson(argument),
|
||||||
|
true);
|
||||||
|
return Mono.just(invocation.getArgument(0));
|
||||||
|
});
|
||||||
|
|
||||||
|
themeService.resetSettingConfig("fake-theme")
|
||||||
|
.as(StepVerifier::create)
|
||||||
|
.consumeNextWith(next -> {
|
||||||
|
assertThat(next).isNotNull();
|
||||||
|
})
|
||||||
|
.verifyComplete();
|
||||||
|
|
||||||
|
verify(client, times(1))
|
||||||
|
.fetch(eq(Setting.class), eq(setting.getMetadata().getName()));
|
||||||
|
|
||||||
|
verify(client, times(1)).fetch(eq(ConfigMap.class), eq("fake-config"));
|
||||||
|
|
||||||
|
verify(client, times(1)).update(any(ConfigMap.class));
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue