refactor: theme route pattern and permalink indexer (#2397)

#### What type of PR is this?
/kind improvement
/milestone 2.0
/area core

#### What this PR does / why we need it:
使用正则细化主题端路由并优化

如何测试:
1. 在 admin 系统设置中修改文章文章详情页访问规则
2. 根据规则访问文章详情页,如规则为:`/{year:\d{4}}/{month:\d{2}}/{slug}` 而存在文章 slug 为 fake-slug 且发布日期为 2022-09-08 则 /2022/09/fake-slug 能访问, /2022/9/fake-slug 则不能访问
使用规则 `/{year:\d{4}}/{month:\d{2}}/{day:\d{2}}/{slug}`时 /2022/09/08/fake-slug 能访问 /2022/09/8/fake-slug ,则不能访问
#### Which issue(s) this PR fixes:
Fixes #2396

#### Special notes for your reviewer:
/cc @halo-dev/sig-halo 
#### Does this PR introduce a user-facing change?
```release-note
None
```
pull/2415/head
guqing 2022-09-13 11:58:10 +08:00 committed by GitHub
parent 9dc7bc1729
commit 069ff04c84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 186 additions and 183 deletions

View File

@ -58,8 +58,8 @@ public class CategoryPermalinkPolicy
@Override
public void onPermalinkDelete(Category category) {
applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, getLocator(category),
category.getStatusOrDefault().getPermalink()));
applicationContext.publishEvent(
new PermalinkIndexDeleteCommand(this, getLocator(category)));
}
private ExtensionLocator getLocator(Category category) {

View File

@ -11,6 +11,20 @@ import run.halo.app.extension.GroupVersionKind;
* @param slug extension slug
*/
public record ExtensionLocator(GroupVersionKind gvk, String name, String slug) {
/**
* Create a new {@link ExtensionLocator} instance.
*
* @param gvk group version kind
* @param name extension name
* @param slug extension slug
*/
public ExtensionLocator {
Objects.requireNonNull(gvk, "Group version kind must not be null");
Objects.requireNonNull(name, "Extension name must not be null");
Objects.requireNonNull(slug, "Extension slug must not be null");
}
@Override
public boolean equals(Object o) {
if (this == o) {

View File

@ -10,6 +10,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import run.halo.app.core.extension.Post;
import run.halo.app.extension.GroupVersionKind;
import run.halo.app.infra.utils.PathUtils;
import run.halo.app.theme.DefaultTemplateEnum;
import run.halo.app.theme.router.PermalinkIndexAddCommand;
import run.halo.app.theme.router.PermalinkIndexDeleteCommand;
@ -23,7 +24,6 @@ import run.halo.app.theme.router.PermalinkWatch;
*/
@Component
public class PostPermalinkPolicy implements PermalinkPolicy<Post>, PermalinkWatch<Post> {
private final GroupVersionKind gvk = GroupVersionKind.fromExtension(Post.class);
private static final NumberFormat NUMBER_FORMAT = new DecimalFormat("00");
@ -65,8 +65,7 @@ public class PostPermalinkPolicy implements PermalinkPolicy<Post>, PermalinkWatc
@Override
public void onPermalinkDelete(Post post) {
applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, getLocator(post),
post.getStatusOrDefault().getPermalink()));
applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, getLocator(post)));
}
private ExtensionLocator getLocator(Post post) {
@ -85,6 +84,8 @@ public class PostPermalinkPolicy implements PermalinkPolicy<Post>, PermalinkWatc
properties.put("year", String.valueOf(zonedDateTime.getYear()));
properties.put("month", NUMBER_FORMAT.format(zonedDateTime.getMonthValue()));
properties.put("day", NUMBER_FORMAT.format(zonedDateTime.getDayOfMonth()));
return PROPERTY_PLACEHOLDER_HELPER.replacePlaceholders(pattern, properties);
String simplifiedPattern = PathUtils.simplifyPathPattern(pattern);
return PROPERTY_PLACEHOLDER_HELPER.replacePlaceholders(simplifiedPattern, properties);
}
}

View File

@ -57,8 +57,7 @@ public class TagPermalinkPolicy implements PermalinkPolicy<Tag>, PermalinkWatch<
@Override
public void onPermalinkDelete(Tag tag) {
applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, getLocator(tag),
tag.getStatusOrDefault().getPermalink()));
applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, getLocator(tag)));
}
private ExtensionLocator getLocator(Tag tag) {

View File

@ -133,8 +133,7 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
.setPermalink(PathUtils.combinePath(singlePage.getSpec().getSlug()));
ExtensionLocator locator = new ExtensionLocator(GVK, singlePage.getMetadata().getName(),
singlePage.getSpec().getSlug());
applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, locator,
singlePage.getStatusOrDefault().getPermalink()));
applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, locator));
templateRouteManager.changeTemplatePattern(DefaultTemplateEnum.SINGLE_PAGE.getValue());
}

View File

@ -47,4 +47,35 @@ public class PathUtils {
public static String appendPathSeparatorIfMissing(String path) {
return StringUtils.appendIfMissing(path, "/", "/");
}
/**
* <p>Remove the regex in the path pattern placeholder.</p>
* <p>For example: </p>
* <ul>
* <li>'{@code /{year:\d{4}}/{month:\d{2}}}' &rarr; '{@code /{year}/{month}}'</li>
* <li>'{@code /archives/{year:\d{4}}/{month:\d{2}}}' &rarr; '{@code /archives/{year}/{month}
* }'</li>
* <li>'{@code /archives/{year:\d{4}}/{slug}}' &rarr; '{@code /archives/{year}/{slug}}'</li>
* </ul>
*
* @param pattern path pattern
* @return Simplified path pattern
*/
public static String simplifyPathPattern(String pattern) {
if (StringUtils.isBlank(pattern)) {
return StringUtils.EMPTY;
}
String[] parts = StringUtils.split(pattern, '/');
for (int i = 0; i < parts.length; i++) {
String part = parts[i];
if (part.startsWith("{") && part.endsWith("}")) {
int colonIdx = part.indexOf(':');
if (colonIdx != -1) {
parts[i] = part.substring(0, colonIdx) + part.charAt(part.length() - 1);
}
}
}
return combinePath(parts);
}
}

View File

@ -12,19 +12,13 @@ import run.halo.app.content.permalinks.ExtensionLocator;
*/
public class PermalinkIndexDeleteCommand extends ApplicationEvent {
private final ExtensionLocator locator;
private final String permalink;
public PermalinkIndexDeleteCommand(Object source, ExtensionLocator locator, String permalink) {
public PermalinkIndexDeleteCommand(Object source, ExtensionLocator locator) {
super(source);
this.locator = locator;
this.permalink = permalink;
}
public ExtensionLocator getLocator() {
return locator;
}
public String getPermalink() {
return permalink;
}
}

View File

@ -3,12 +3,11 @@ package run.halo.app.theme.router;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.springframework.context.event.EventListener;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import run.halo.app.content.permalinks.ExtensionLocator;
import run.halo.app.extension.GroupVersionKind;
@ -21,9 +20,12 @@ import run.halo.app.extension.GroupVersionKind;
@Component
public class PermalinkIndexer {
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final MultiValueMap<GroupVersionKind, String> permalinkLookup =
new LinkedMultiValueMap<>();
private final Map<String, ExtensionLocator> permalinkLocatorMap = new HashMap<>();
private final Map<GvkName, String> gvkNamePermalinkLookup = new HashMap<>();
private final Map<String, ExtensionLocator> permalinkLocatorLookup = new HashMap<>();
record GvkName(GroupVersionKind gvk, String name) {
}
/**
* Register extension and permalink mapping.
@ -34,8 +36,9 @@ public class PermalinkIndexer {
public void register(ExtensionLocator locator, String permalink) {
readWriteLock.writeLock().lock();
try {
permalinkLookup.add(locator.gvk(), permalink);
permalinkLocatorMap.put(permalink, locator);
GvkName gvkName = new GvkName(locator.gvk(), locator.name());
gvkNamePermalinkLookup.put(gvkName, permalink);
permalinkLocatorLookup.put(permalink, locator);
} finally {
readWriteLock.writeLock().unlock();
}
@ -44,110 +47,87 @@ public class PermalinkIndexer {
/**
* Remove extension and permalink mapping.
*
* @param locator extension locator
* @param permalink extension permalink for theme template route
* @param locator extension info
*/
public void remove(ExtensionLocator locator, String permalink) {
public void remove(ExtensionLocator locator) {
readWriteLock.writeLock().lock();
try {
List<String> permalinks = permalinkLookup.get(locator.gvk());
if (permalinks != null) {
permalinks.remove(permalink);
if (permalinks.isEmpty()) {
permalinkLookup.remove(locator.gvk());
String permalink =
gvkNamePermalinkLookup.remove(new GvkName(locator.gvk(), locator.name()));
if (permalink != null) {
permalinkLocatorLookup.remove(permalink);
}
}
permalinkLocatorMap.remove(permalink);
} finally {
readWriteLock.writeLock().unlock();
}
}
/**
* Lookup extension locator by permalink.
* Gets permalink by {@link GroupVersionKind}.
*
* @param gvk group version kind
* @return permalinks
*/
@NonNull
public List<String> getPermalinks(GroupVersionKind gvk) {
readWriteLock.readLock().lock();
try {
return gvkNamePermalinkLookup.entrySet()
.stream()
.filter(entry -> entry.getKey().gvk.equals(gvk))
.map(Map.Entry::getValue)
.toList();
} finally {
readWriteLock.readLock().unlock();
}
}
/**
* Lookup extension info by permalink.
*
* @param permalink extension permalink for theme template route
* @return extension locator
*/
@Nullable
public ExtensionLocator lookup(String permalink) {
readWriteLock.readLock().lock();
try {
return permalinkLocatorMap.get(permalink);
return permalinkLocatorLookup.get(permalink);
} finally {
readWriteLock.readLock().unlock();
}
}
/**
* Gets permalinks by extension's {@link GroupVersionKind}.
* Lookup extension permalink by {@link GroupVersionKind} and {@code name}.
*
* @param gvk extension's {@link GroupVersionKind}
* @return permalinks for extension's {@link GroupVersionKind}
* @param gvk group version kind
* @param name extension name
* @return {@code true} if contains, otherwise {@code false}
*/
public List<String> getPermalinks(GroupVersionKind gvk) {
public boolean containsName(GroupVersionKind gvk, String name) {
readWriteLock.readLock().lock();
try {
return Objects.requireNonNullElse(permalinkLookup.get(gvk), List.of());
return gvkNamePermalinkLookup.containsKey(new GvkName(gvk, name));
} finally {
readWriteLock.readLock().unlock();
}
}
/**
* Lookup extension resource names by {@link GroupVersionKind}.
* Lookup extension permalink by {@link GroupVersionKind} and {@code slug}.
*
* @param gvk extension's {@link GroupVersionKind}
* @return extension resource names
* @param gvk group version kind
* @param slug extension slug
* @return {@code true} if contains, otherwise {@code false}
*/
public List<String> getNames(GroupVersionKind gvk) {
public boolean containsSlug(GroupVersionKind gvk, String slug) {
readWriteLock.readLock().lock();
try {
return permalinkLocatorMap.values()
return permalinkLocatorLookup.values()
.stream()
.filter(locator -> locator.gvk().equals(gvk))
.map(ExtensionLocator::name)
.toList();
} finally {
readWriteLock.readLock().unlock();
}
}
/**
* Lookup extension resource slugs by {@link GroupVersionKind}.
*
* @param gvk extension's {@link GroupVersionKind}
* @return extension resource slugs
*/
public List<String> getSlugs(GroupVersionKind gvk) {
readWriteLock.readLock().lock();
try {
return permalinkLocatorMap.values()
.stream()
.filter(locator -> locator.gvk().equals(gvk))
.map(ExtensionLocator::slug)
.toList();
} finally {
readWriteLock.readLock().unlock();
}
}
/**
* Lookup extension slug by resource name.
*
* @param gvk extension's {@link GroupVersionKind}
* @param name extension resource name
* @return extension slug specified by resource name
*/
public String getSlugByName(GroupVersionKind gvk, String name) {
readWriteLock.readLock().lock();
try {
return permalinkLocatorMap.values()
.stream()
.filter(locator -> locator.gvk().equals(gvk))
.filter(locator -> locator.name().equals(name))
.findFirst()
.map(ExtensionLocator::slug)
.orElseThrow();
.anyMatch(locator -> locator.gvk().equals(gvk)
&& locator.slug().equals(slug));
} finally {
readWriteLock.readLock().unlock();
}
@ -163,10 +143,10 @@ public class PermalinkIndexer {
public String getNameBySlug(GroupVersionKind gvk, String slug) {
readWriteLock.readLock().lock();
try {
return permalinkLocatorMap.values()
return permalinkLocatorLookup.values()
.stream()
.filter(locator -> locator.gvk().equals(gvk))
.filter(locator -> locator.slug().equals(slug))
.filter(locator -> locator.gvk().equals(gvk)
&& locator.slug().equals(slug))
.findFirst()
.map(ExtensionLocator::name)
.orElseThrow();
@ -180,8 +160,8 @@ public class PermalinkIndexer {
*
* @return permalinkLookup map size
*/
protected long permalinkLookupSize() {
return permalinkLookup.size();
protected long gvkNamePermalinkMapSize() {
return gvkNamePermalinkLookup.size();
}
/**
@ -190,7 +170,7 @@ public class PermalinkIndexer {
* @return permalinkLocatorMap map size
*/
protected long permalinkLocatorMapSize() {
return permalinkLocatorMap.size();
return permalinkLocatorLookup.size();
}
@EventListener(PermalinkIndexAddCommand.class)
@ -200,12 +180,12 @@ public class PermalinkIndexer {
@EventListener(PermalinkIndexDeleteCommand.class)
public void onPermalinkDelete(PermalinkIndexDeleteCommand deleteCommand) {
remove(deleteCommand.getLocator(), deleteCommand.getPermalink());
remove(deleteCommand.getLocator());
}
@EventListener(PermalinkIndexUpdateCommand.class)
public void onPermalinkUpdate(PermalinkIndexUpdateCommand updateCommand) {
remove(updateCommand.getLocator(), updateCommand.getPermalink());
remove(updateCommand.getLocator());
register(updateCommand.getLocator(), updateCommand.getPermalink());
}
}

View File

@ -40,9 +40,10 @@ public class ArchivesRouteStrategy implements TemplateRouterStrategy {
public RouterFunction<ServerResponse> getRouteFunction(String template, String prefix) {
return RouterFunctions
.route(GET(prefix)
.or(GET(PathUtils.combinePath(prefix, "/page/{page}")))
.or(GET(PathUtils.combinePath(prefix, "/{year}/{month}")))
.or(GET(PathUtils.combinePath(prefix, "/{year}/{month}/page/{page}")))
.or(GET(PathUtils.combinePath(prefix, "/page/{page:\\d+}")))
.or(GET(PathUtils.combinePath(prefix, "/{year:\\d{4}}/{month:\\d{2}}")))
.or(GET(PathUtils.combinePath(prefix,
"/{year:\\d{4}}/{month:\\d{2}}/page/{page:\\d+}")))
.and(accept(MediaType.TEXT_HTML)),
request -> ServerResponse.ok()
.render(DefaultTemplateEnum.ARCHIVES.getValue(),

View File

@ -5,7 +5,6 @@ import static org.springframework.web.reactive.function.server.RequestPredicates
import static run.halo.app.theme.router.TemplateRouterStrategy.PageUrlUtils.pageNum;
import static run.halo.app.theme.router.TemplateRouterStrategy.PageUrlUtils.totalPage;
import java.util.List;
import java.util.Map;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
@ -53,13 +52,12 @@ public class CategoryRouteStrategy implements TemplateRouterStrategy {
public RouterFunction<ServerResponse> getRouteFunction(String template, String prefix) {
return RouterFunctions
.route(GET(PathUtils.combinePath(prefix, "/{slug}"))
.or(GET(PathUtils.combinePath(prefix, "/{slug}/page/{page}")))
.or(GET(PathUtils.combinePath(prefix, "/{slug}/page/{page:\\d+}")))
.and(accept(MediaType.TEXT_HTML)),
request -> {
String slug = request.pathVariable("slug");
GroupVersionKind gvk = GroupVersionKind.fromExtension(Category.class);
List<String> slugs = permalinkIndexer.getSlugs(gvk);
if (!slugs.contains(slug)) {
if (!permalinkIndexer.containsSlug(gvk, slug)) {
return ServerResponse.notFound().build();
}
String categoryName = permalinkIndexer.getNameBySlug(gvk, slug);

View File

@ -39,7 +39,7 @@ public class IndexRouteStrategy implements TemplateRouterStrategy {
@Override
public RouterFunction<ServerResponse> getRouteFunction(String template, String pattern) {
return RouterFunctions
.route(GET("/").or(GET("/page/{page}"))
.route(GET("/").or(GET("/page/{page:\\d+}"))
.and(accept(MediaType.TEXT_HTML)),
request -> ServerResponse.ok()
.render(DefaultTemplateEnum.INDEX.getValue(),

View File

@ -4,7 +4,6 @@ import static org.springframework.web.reactive.function.server.RequestPredicates
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -75,32 +74,27 @@ public class PostRouteStrategy implements TemplateRouterStrategy {
}
return ServerResponse.ok()
.render(DefaultTemplateEnum.POST.getValue(),
Map.of(PostRequestParamPredicate.NAME_PARAM, name)
Map.of(PostRequestParamPredicate.NAME_PARAM, name,
"post", postByName(name))
);
});
}
return RouterFunctions
.route(GET(pattern).and(accept(MediaType.TEXT_HTML)),
request -> {
List<String> permalinks =
permalinkIndexer.getPermalinks(
PostRequestParamPredicate.GVK);
boolean contains = permalinks.contains(request.path());
if (!contains) {
ExtensionLocator locator = permalinkIndexer.lookup(request.path());
if (locator == null) {
return ServerResponse.notFound().build();
}
ExtensionLocator extensionLocator =
permalinkIndexer.lookup(request.path());
PathPattern parse = PathPatternParser.defaultInstance.parse(pattern);
PathPattern.PathMatchInfo pathMatchInfo =
parse.matchAndExtract(PathContainer.parsePath(request.path()));
Map<String, Object> model = new HashMap<>();
model.put(PostRequestParamPredicate.NAME_PARAM,
extensionLocator.name());
model.put(PostRequestParamPredicate.NAME_PARAM, locator.name());
if (pathMatchInfo != null) {
model.putAll(pathMatchInfo.getUriVariables());
}
model.put("post", postByName(extensionLocator.name()));
model.put("post", postByName(locator.name()));
return ServerResponse.ok()
.render(DefaultTemplateEnum.POST.getValue(), model);
});
@ -139,17 +133,13 @@ public class PostRouteStrategy implements TemplateRouterStrategy {
}
if (NAME_PARAM.equals(placeholderName)) {
return RequestPredicates.queryParam(paramName, name -> {
List<String> names = permalinkIndexer.getNames(GVK);
return names.contains(name);
});
return RequestPredicates.queryParam(paramName,
name -> permalinkIndexer.containsName(GVK, name));
}
if (SLUG_PARAM.equals(placeholderName)) {
return RequestPredicates.queryParam(paramName, slug -> {
List<String> slugs = permalinkIndexer.getSlugs(GVK);
return slugs.contains(slug);
});
return RequestPredicates.queryParam(paramName,
slug -> permalinkIndexer.containsSlug(GVK, slug));
}
throw new IllegalArgumentException(
String.format("Unknown param value placeholder [%s] in pattern [%s]",

View File

@ -4,7 +4,6 @@ import static org.springframework.web.reactive.function.server.RequestPredicates
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
@ -48,8 +47,7 @@ public class SinglePageRouteStrategy implements TemplateRouterStrategy {
RequestPredicate requestPredicate = request -> false;
List<String> permalinks =
Objects.requireNonNullElse(permalinkIndexer.getPermalinks(gvk), List.of());
List<String> permalinks = permalinkIndexer.getPermalinks(gvk);
for (String permalink : permalinks) {
requestPredicate = requestPredicate.or(RequestPredicates.GET(permalink));
}

View File

@ -5,7 +5,6 @@ import static org.springframework.web.reactive.function.server.RequestPredicates
import static run.halo.app.theme.router.TemplateRouterStrategy.PageUrlUtils.pageNum;
import static run.halo.app.theme.router.TemplateRouterStrategy.PageUrlUtils.totalPage;
import java.util.List;
import java.util.Map;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
@ -53,15 +52,15 @@ public class TagRouteStrategy implements TemplateRouterStrategy {
public RouterFunction<ServerResponse> getRouteFunction(String template, String prefix) {
return RouterFunctions
.route(GET(PathUtils.combinePath(prefix, "/{slug}"))
.or(GET(PathUtils.combinePath(prefix, "/{slug}/page/{page}")))
.or(GET(PathUtils.combinePath(prefix, "/{slug}/page/{page:\\d+}")))
.and(accept(MediaType.TEXT_HTML)),
request -> {
GroupVersionKind gvk = GroupVersionKind.fromExtension(Tag.class);
List<String> slugs = permalinkIndexer.getSlugs(gvk);
String slug = request.pathVariable("slug");
if (!slugs.contains(slug)) {
if (!permalinkIndexer.containsSlug(gvk, slug)) {
return ServerResponse.notFound().build();
}
String name = permalinkIndexer.getNameBySlug(gvk, slug);
return ServerResponse.ok()
.render(DefaultTemplateEnum.TAG.getValue(),

View File

@ -32,7 +32,7 @@ spec:
label: "默认角色"
name: defaultRole
validation: required
- group: themeRules
- group: routeRules
label: 主题模板路由设置
formSchema:
- $formkit: text
@ -58,9 +58,9 @@ spec:
- /archives/{name}
- /?p={name}
- /?p={slug}
- /{year}/{slug}
- /{year}/{month}/{slug}
- /{year}/{month}/{day}/{slug}
- /{year:\d{4}}/{slug}
- /{year:\d{4}}/{month:\d{2}}/{slug}
- /{year:\d{4}}/{month:\d{2}}/{day:\d{2}}/{slug}
name: post
validation: required
- group: post

View File

@ -43,4 +43,17 @@ class PathUtilsTest {
s = PathUtils.appendPathSeparatorIfMissing(null);
assertThat(s).isEqualTo(null);
}
@Test
void simplifyPathPattern() {
assertThat(PathUtils.simplifyPathPattern("/a/b/c")).isEqualTo("/a/b/c");
assertThat(PathUtils.simplifyPathPattern("/a/{b}/c")).isEqualTo("/a/{b}/c");
assertThat(PathUtils.simplifyPathPattern("/a/{b}/*")).isEqualTo("/a/{b}/*");
assertThat(PathUtils.simplifyPathPattern("/archives/{year:\\d{4}}/{month:\\d{2}}"))
.isEqualTo("/archives/{year}/{month}");
assertThat(PathUtils.simplifyPathPattern("/archives/{year:\\d{4}}/{slug}"))
.isEqualTo("/archives/{year}/{slug}");
assertThat(PathUtils.simplifyPathPattern("/archives/{year:\\d{4}}/page/{page:\\d+}"))
.isEqualTo("/archives/{year}/page/{page}");
}
}

View File

@ -49,7 +49,7 @@ class PermalinkIndexerTest {
assertThat(permalinkIndexer.permalinkLocatorMapSize()).isEqualTo(1);
assertThat(permalinkIndexer.permalinkLocatorMapSize()).isEqualTo(1);
permalinkIndexer.remove(locator, "/fake-permalink");
permalinkIndexer.remove(locator);
assertThat(permalinkIndexer.permalinkLocatorMapSize()).isEqualTo(0);
assertThat(permalinkIndexer.permalinkLocatorMapSize()).isEqualTo(0);
}
@ -77,8 +77,8 @@ class PermalinkIndexerTest {
ExtensionLocator locator = new ExtensionLocator(gvk, "test-name", "test-slug");
permalinkIndexer.register(locator, "/test-permalink");
List<String> names = permalinkIndexer.getNames(gvk);
assertThat(names).isEqualTo(List.of("fake-name", "test-name"));
assertThat(permalinkIndexer.containsName(gvk, "test-name")).isTrue();
assertThat(permalinkIndexer.containsName(gvk, "nothing")).isFalse();
}
@Test
@ -86,24 +86,9 @@ class PermalinkIndexerTest {
ExtensionLocator locator = new ExtensionLocator(gvk, "test-name", "test-slug");
permalinkIndexer.register(locator, "/test-permalink");
List<String> slugs = permalinkIndexer.getSlugs(gvk);
assertThat(slugs).isEqualTo(List.of("fake-slug", "test-slug"));
}
@Test
void getSlugByName() {
ExtensionLocator locator = new ExtensionLocator(gvk, "test-name", "test-slug");
permalinkIndexer.register(locator, "/test-permalink");
String slugByName = permalinkIndexer.getSlugByName(gvk, "test-name");
assertThat(slugByName).isEqualTo("test-slug");
slugByName = permalinkIndexer.getSlugByName(gvk, "fake-name");
assertThat(slugByName).isEqualTo("fake-slug");
assertThatThrownBy(() -> {
permalinkIndexer.getSlugByName(gvk, "nothing");
}).isInstanceOf(NoSuchElementException.class);
assertThat(permalinkIndexer.containsSlug(gvk, "fake-slug")).isTrue();
assertThat(permalinkIndexer.containsSlug(gvk, "test-slug")).isTrue();
assertThat(permalinkIndexer.containsSlug(gvk, "nothing")).isFalse();
}
@Test

View File

@ -82,14 +82,20 @@ class ArchivesRouteStrategyTest {
.expectStatus().isOk();
client.get()
.uri(prefix + "/year/month")
.uri(prefix + "/2022/09")
.exchange()
.expectStatus().isOk();
client.get()
.uri(prefix + "/year/month/page/1")
.uri(prefix + "/2022/08/page/1")
.exchange()
.expectStatus().isOk();
client.get()
.uri(prefix + "/2022/8/page/1")
.exchange()
.expectStatus()
.isEqualTo(HttpStatus.NOT_FOUND);
}
@Test

View File

@ -20,6 +20,8 @@ import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.Category;
import run.halo.app.extension.GroupVersionKind;
import run.halo.app.extension.ListResult;
import run.halo.app.theme.DefaultTemplateEnum;
import run.halo.app.theme.finders.PostFinder;
@ -47,8 +49,11 @@ class CategoryRouteStrategyTest {
@BeforeEach
void setUp() {
when(permalinkIndexer.getSlugs(any()))
.thenReturn(List.of("category-slug-1", "category-slug-2"));
GroupVersionKind gvk = GroupVersionKind.fromExtension(Category.class);
when(permalinkIndexer.containsSlug(eq(gvk), eq("category-slug-1")))
.thenReturn(true);
when(permalinkIndexer.containsSlug(eq(gvk), eq("category-slug-2")))
.thenReturn(true);
when(permalinkIndexer.getNameBySlug(any(), eq("category-slug-1")))
.thenReturn("category-name-1");
when(permalinkIndexer.getNameBySlug(any(), eq("category-slug-2")))

View File

@ -5,7 +5,6 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -64,9 +63,6 @@ class PostRouteStrategyTest {
piling();
when(permalinkIndexer.getPermalinks(any()))
.thenReturn(List.of("/posts-test/fake-slug"));
client.get()
.uri("/posts-test/fake-slug")
.exchange()
@ -84,9 +80,6 @@ class PostRouteStrategyTest {
piling();
when(permalinkIndexer.getPermalinks(any()))
.thenReturn(List.of("/posts-test/fake-name"));
client.get()
.uri("/posts-test/fake-name")
.exchange()
@ -104,9 +97,6 @@ class PostRouteStrategyTest {
piling();
when(permalinkIndexer.getPermalinks(any()))
.thenReturn(List.of("/2022/08/fake-slug"));
client.get()
.uri("/2022/08/fake-slug")
.exchange()
@ -144,14 +134,11 @@ class PostRouteStrategyTest {
}
private void piling() {
lenient().when(permalinkIndexer.getNames(any()))
.thenReturn(List.of("fake-name"));
lenient().when(permalinkIndexer.getSlugs(any()))
.thenReturn(List.of("fake-slug"));
lenient().when(permalinkIndexer.getSlugs(any()))
.thenReturn(List.of("fake-slug"));
GroupVersionKind postGvk = GroupVersionKind.fromExtension(Post.class);
lenient().when(permalinkIndexer.containsName(eq(postGvk), eq("fake-name")))
.thenReturn(true);
lenient().when(permalinkIndexer.containsSlug(eq(postGvk), eq("fake-slug")))
.thenReturn(true);
lenient().when(permalinkIndexer.getNameBySlug(any(), eq("fake-slug")))
.thenReturn("fake-name");

View File

@ -20,6 +20,8 @@ import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.Tag;
import run.halo.app.extension.GroupVersionKind;
import run.halo.app.extension.ListResult;
import run.halo.app.theme.DefaultTemplateEnum;
import run.halo.app.theme.finders.PostFinder;
@ -48,8 +50,9 @@ class TagRouteStrategyTest {
void setUp() {
lenient().when(postFinder.listByTag(anyInt(), anyInt(), any()))
.thenReturn(new ListResult<>(1, 10, 0, List.of()));
when(permalinkIndexer.getSlugs(any()))
.thenReturn(List.of("fake-slug"));
GroupVersionKind gvk = GroupVersionKind.fromExtension(Tag.class);
when(permalinkIndexer.containsSlug(eq(gvk), eq("fake-slug")))
.thenReturn(true);
when(permalinkIndexer.getNameBySlug(any(), eq("fake-slug")))
.thenReturn("fake-name");
}