diff --git a/src/main/java/run/halo/app/core/extension/User.java b/src/main/java/run/halo/app/core/extension/User.java index 33dd209f02..b28a51025f 100644 --- a/src/main/java/run/halo/app/core/extension/User.java +++ b/src/main/java/run/halo/app/core/extension/User.java @@ -71,6 +71,8 @@ public static class UserStatus { private Instant lastLoginAt; + private String permalink; + private List loginHistories; } diff --git a/src/main/java/run/halo/app/core/extension/reconciler/UserReconciler.java b/src/main/java/run/halo/app/core/extension/reconciler/UserReconciler.java index 5aed2b27b5..bdcbf0a744 100644 --- a/src/main/java/run/halo/app/core/extension/reconciler/UserReconciler.java +++ b/src/main/java/run/halo/app/core/extension/reconciler/UserReconciler.java @@ -1,30 +1,113 @@ package run.halo.app.core.extension.reconciler; +import java.util.HashSet; +import java.util.Set; +import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; +import run.halo.app.content.permalinks.ExtensionLocator; import run.halo.app.core.extension.User; import run.halo.app.extension.ExtensionClient; +import run.halo.app.extension.GroupVersionKind; import run.halo.app.extension.controller.Controller; import run.halo.app.extension.controller.ControllerBuilder; import run.halo.app.extension.controller.Reconciler; import run.halo.app.extension.controller.Reconciler.Request; +import run.halo.app.infra.AnonymousUserConst; +import run.halo.app.infra.ExternalUrlSupplier; +import run.halo.app.infra.utils.PathUtils; +import run.halo.app.theme.router.PermalinkIndexDeleteCommand; +import run.halo.app.theme.router.PermalinkIndexUpdateCommand; @Slf4j @Component +@AllArgsConstructor public class UserReconciler implements Reconciler { - + private static final String FINALIZER_NAME = "user-protection"; private final ExtensionClient client; - - public UserReconciler(ExtensionClient client) { - this.client = client; - } + private final ApplicationEventPublisher eventPublisher; + private final ExternalUrlSupplier externalUrlSupplier; @Override public Result reconcile(Request request) { - //TODO Add reconciliation logic here for User extension. + client.fetch(User.class, request.name()).ifPresent(user -> { + if (user.getMetadata().getDeletionTimestamp() != null) { + cleanUpResourcesAndRemoveFinalizer(request.name()); + return; + } + + addFinalizerIfNecessary(user); + updatePermalink(request.name()); + }); return new Result(false, null); } + private void updatePermalink(String name) { + client.fetch(User.class, name).ifPresent(user -> { + if (AnonymousUserConst.isAnonymousUser(name)) { + // anonymous user is not allowed to have permalink + return; + } + if (user.getStatus() == null) { + user.setStatus(new User.UserStatus()); + } + User.UserStatus status = user.getStatus(); + String oldPermalink = status.getPermalink(); + + status.setPermalink(getUserPermalink(user)); + + ExtensionLocator extensionLocator = getExtensionLocator(name); + eventPublisher.publishEvent( + new PermalinkIndexUpdateCommand(this, extensionLocator, status.getPermalink())); + + if (!StringUtils.equals(oldPermalink, status.getPermalink())) { + client.update(user); + } + }); + } + + private static ExtensionLocator getExtensionLocator(String name) { + return new ExtensionLocator(GroupVersionKind.fromExtension(User.class), name, + name); + } + + private String getUserPermalink(User user) { + return externalUrlSupplier.get() + .resolve(PathUtils.combinePath("authors", user.getMetadata().getName())) + .normalize().toString(); + } + + private void addFinalizerIfNecessary(User oldUser) { + Set finalizers = oldUser.getMetadata().getFinalizers(); + if (finalizers != null && finalizers.contains(FINALIZER_NAME)) { + return; + } + client.fetch(User.class, oldUser.getMetadata().getName()) + .ifPresent(user -> { + Set newFinalizers = user.getMetadata().getFinalizers(); + if (newFinalizers == null) { + newFinalizers = new HashSet<>(); + user.getMetadata().setFinalizers(newFinalizers); + } + newFinalizers.add(FINALIZER_NAME); + client.update(user); + }); + } + + private void cleanUpResourcesAndRemoveFinalizer(String userName) { + client.fetch(User.class, userName).ifPresent(user -> { + eventPublisher.publishEvent( + new PermalinkIndexDeleteCommand(this, getExtensionLocator(userName))); + + if (user.getMetadata().getFinalizers() != null) { + user.getMetadata().getFinalizers().remove(FINALIZER_NAME); + } + client.update(user); + }); + } + @Override public Controller setupWith(ControllerBuilder builder) { return builder diff --git a/src/main/java/run/halo/app/infra/AnonymousUserConst.java b/src/main/java/run/halo/app/infra/AnonymousUserConst.java index 9981295e89..5bdced6d32 100644 --- a/src/main/java/run/halo/app/infra/AnonymousUserConst.java +++ b/src/main/java/run/halo/app/infra/AnonymousUserConst.java @@ -4,4 +4,8 @@ public interface AnonymousUserConst { String PRINCIPAL = "anonymousUser"; String Role = "anonymous"; + + static boolean isAnonymousUser(String principal) { + return PRINCIPAL.equals(principal); + } } diff --git a/src/main/java/run/halo/app/theme/DefaultTemplateEnum.java b/src/main/java/run/halo/app/theme/DefaultTemplateEnum.java index 56d04fd65a..9738d94c59 100644 --- a/src/main/java/run/halo/app/theme/DefaultTemplateEnum.java +++ b/src/main/java/run/halo/app/theme/DefaultTemplateEnum.java @@ -19,7 +19,9 @@ public enum DefaultTemplateEnum { TAGS("tags"), - SINGLE_PAGE("page"); + SINGLE_PAGE("page"), + + AUTHOR("author"); private final String value; diff --git a/src/main/java/run/halo/app/theme/finders/ContributorFinder.java b/src/main/java/run/halo/app/theme/finders/ContributorFinder.java index fa1b163176..904bf57eb1 100644 --- a/src/main/java/run/halo/app/theme/finders/ContributorFinder.java +++ b/src/main/java/run/halo/app/theme/finders/ContributorFinder.java @@ -4,14 +4,14 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import run.halo.app.core.extension.User; -import run.halo.app.theme.finders.vo.Contributor; +import run.halo.app.theme.finders.vo.ContributorVo; /** * A finder for {@link User}. */ public interface ContributorFinder { - Mono getContributor(String name); + Mono getContributor(String name); - Flux getContributors(List names); + Flux getContributors(List names); } diff --git a/src/main/java/run/halo/app/theme/finders/PostFinder.java b/src/main/java/run/halo/app/theme/finders/PostFinder.java index a179f38322..be2ba02a0c 100644 --- a/src/main/java/run/halo/app/theme/finders/PostFinder.java +++ b/src/main/java/run/halo/app/theme/finders/PostFinder.java @@ -35,6 +35,9 @@ Mono> listByCategory(@Nullable Integer page, @Nullable Mono> listByTag(@Nullable Integer page, @Nullable Integer size, String tag); + Mono> listByOwner(@Nullable Integer page, @Nullable Integer size, + String owner); + Mono> archives(Integer page, Integer size); Mono> archives(Integer page, Integer size, String year); diff --git a/src/main/java/run/halo/app/theme/finders/impl/ContributorFinderImpl.java b/src/main/java/run/halo/app/theme/finders/impl/ContributorFinderImpl.java index 60e6b7e558..42aacf3e58 100644 --- a/src/main/java/run/halo/app/theme/finders/impl/ContributorFinderImpl.java +++ b/src/main/java/run/halo/app/theme/finders/impl/ContributorFinderImpl.java @@ -7,7 +7,7 @@ import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.theme.finders.ContributorFinder; import run.halo.app.theme.finders.Finder; -import run.halo.app.theme.finders.vo.Contributor; +import run.halo.app.theme.finders.vo.ContributorVo; /** * A default implementation of {@link ContributorFinder}. @@ -25,13 +25,13 @@ public ContributorFinderImpl(ReactiveExtensionClient client) { } @Override - public Mono getContributor(String name) { + public Mono getContributor(String name) { return client.fetch(User.class, name) - .map(Contributor::from); + .map(ContributorVo::from); } @Override - public Flux getContributors(List names) { + public Flux getContributors(List names) { if (names == null) { return Flux.empty(); } diff --git a/src/main/java/run/halo/app/theme/finders/impl/PostFinderImpl.java b/src/main/java/run/halo/app/theme/finders/impl/PostFinderImpl.java index 56a954106a..eb20974b3d 100644 --- a/src/main/java/run/halo/app/theme/finders/impl/PostFinderImpl.java +++ b/src/main/java/run/halo/app/theme/finders/impl/PostFinderImpl.java @@ -220,6 +220,12 @@ public Mono> listByTag(Integer page, Integer size, Stri post -> contains(post.getSpec().getTags(), tag), defaultComparator()); } + @Override + public Mono> listByOwner(Integer page, Integer size, String owner) { + return listPost(page, size, + post -> post.getSpec().getOwner().equals(owner), defaultComparator()); + } + @Override public Mono> archives(Integer page, Integer size) { return archives(page, size, null, null); diff --git a/src/main/java/run/halo/app/theme/finders/vo/Contributor.java b/src/main/java/run/halo/app/theme/finders/vo/ContributorVo.java similarity index 68% rename from src/main/java/run/halo/app/theme/finders/vo/Contributor.java rename to src/main/java/run/halo/app/theme/finders/vo/ContributorVo.java index 657cfdfaa3..99eb599f1e 100644 --- a/src/main/java/run/halo/app/theme/finders/vo/Contributor.java +++ b/src/main/java/run/halo/app/theme/finders/vo/ContributorVo.java @@ -14,7 +14,7 @@ @Value @ToString @Builder -public class Contributor { +public class ContributorVo { String name; String displayName; @@ -23,17 +23,22 @@ public class Contributor { String bio; + String permalink; + /** - * Convert {@link User} to {@link Contributor}. + * Convert {@link User} to {@link ContributorVo}. * * @param user user extension * @return contributor value object */ - public static Contributor from(User user) { + public static ContributorVo from(User user) { + User.UserStatus status = user.getStatus(); + String permalink = (status == null ? "" : status.getPermalink()); return builder().name(user.getMetadata().getName()) .displayName(user.getSpec().getDisplayName()) .avatar(user.getSpec().getAvatar()) .bio(user.getSpec().getBio()) + .permalink(permalink) .build(); } } diff --git a/src/main/java/run/halo/app/theme/finders/vo/ListedPostVo.java b/src/main/java/run/halo/app/theme/finders/vo/ListedPostVo.java index a17075118e..dc7e46d30b 100644 --- a/src/main/java/run/halo/app/theme/finders/vo/ListedPostVo.java +++ b/src/main/java/run/halo/app/theme/finders/vo/ListedPostVo.java @@ -31,9 +31,9 @@ public class ListedPostVo { private List tags; - private List contributors; + private List contributors; - private Contributor owner; + private ContributorVo owner; private StatsVo stats; diff --git a/src/main/java/run/halo/app/theme/finders/vo/ListedSinglePageVo.java b/src/main/java/run/halo/app/theme/finders/vo/ListedSinglePageVo.java index eb951d4b81..7ac6d90c59 100644 --- a/src/main/java/run/halo/app/theme/finders/vo/ListedSinglePageVo.java +++ b/src/main/java/run/halo/app/theme/finders/vo/ListedSinglePageVo.java @@ -29,9 +29,9 @@ public class ListedSinglePageVo { private StatsVo stats; - private List contributors; + private List contributors; - private Contributor owner; + private ContributorVo owner; /** * Convert {@link SinglePage} to {@link ListedSinglePageVo}. diff --git a/src/main/java/run/halo/app/theme/finders/vo/UserVo.java b/src/main/java/run/halo/app/theme/finders/vo/UserVo.java new file mode 100644 index 0000000000..cbe0137b41 --- /dev/null +++ b/src/main/java/run/halo/app/theme/finders/vo/UserVo.java @@ -0,0 +1,40 @@ +package run.halo.app.theme.finders.vo; + +import java.util.List; +import lombok.Builder; +import lombok.Value; +import org.apache.commons.lang3.ObjectUtils; +import run.halo.app.core.extension.User; +import run.halo.app.extension.MetadataOperator; +import run.halo.app.infra.utils.JsonUtils; + +@Value +@Builder +public class UserVo { + MetadataOperator metadata; + + User.UserSpec spec; + + User.UserStatus status; + + /** + * Converts to {@link UserVo} from {@link User}. + * + * @param user user extension + * @return user value object. + */ + public static UserVo from(User user) { + User.UserStatus statusCopy = + JsonUtils.deepCopy(ObjectUtils.defaultIfNull(user.getStatus(), new User.UserStatus())); + statusCopy.setLoginHistories(List.of()); + statusCopy.setLastLoginAt(null); + + User.UserSpec userSpecCopy = JsonUtils.deepCopy(user.getSpec()); + userSpecCopy.setPassword("[PROTECTED]"); + return UserVo.builder() + .metadata(user.getMetadata()) + .spec(userSpecCopy) + .status(statusCopy) + .build(); + } +} diff --git a/src/main/java/run/halo/app/theme/router/PermalinkPatternProvider.java b/src/main/java/run/halo/app/theme/router/PermalinkPatternProvider.java index 2942e0895d..4e731915e5 100644 --- a/src/main/java/run/halo/app/theme/router/PermalinkPatternProvider.java +++ b/src/main/java/run/halo/app/theme/router/PermalinkPatternProvider.java @@ -50,7 +50,7 @@ private SystemSetting.ThemeRouteRules getPermalinkRules() { public String getPattern(DefaultTemplateEnum defaultTemplateEnum) { SystemSetting.ThemeRouteRules permalinkRules = getPermalinkRules(); return switch (defaultTemplateEnum) { - case INDEX, SINGLE_PAGE -> null; + case INDEX, SINGLE_PAGE, AUTHOR -> null; case POST -> permalinkRules.getPost(); case ARCHIVES -> permalinkRules.getArchives(); case CATEGORY, CATEGORIES -> permalinkRules.getCategories(); diff --git a/src/main/java/run/halo/app/theme/router/strategy/AuthorRouteStrategy.java b/src/main/java/run/halo/app/theme/router/strategy/AuthorRouteStrategy.java new file mode 100644 index 0000000000..0c243a4997 --- /dev/null +++ b/src/main/java/run/halo/app/theme/router/strategy/AuthorRouteStrategy.java @@ -0,0 +1,76 @@ +package run.halo.app.theme.router.strategy; + +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; +import static run.halo.app.theme.router.PageUrlUtils.pageNum; +import static run.halo.app.theme.router.PageUrlUtils.totalPage; + +import java.util.Map; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.server.HandlerFunction; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; +import run.halo.app.core.extension.User; +import run.halo.app.extension.GroupVersionKind; +import run.halo.app.extension.ReactiveExtensionClient; +import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; +import run.halo.app.infra.SystemSetting; +import run.halo.app.theme.DefaultTemplateEnum; +import run.halo.app.theme.finders.PostFinder; +import run.halo.app.theme.finders.vo.ListedPostVo; +import run.halo.app.theme.finders.vo.UserVo; +import run.halo.app.theme.router.PageUrlUtils; +import run.halo.app.theme.router.UrlContextListResult; + +/** + * Author route strategy. + * + * @author guqing + * @since 2.0.1 + */ +@Component +@AllArgsConstructor +public class AuthorRouteStrategy implements DetailsPageRouteHandlerStrategy { + + private final ReactiveExtensionClient client; + + private final PostFinder postFinder; + + private final SystemConfigurableEnvironmentFetcher environmentFetcher; + + @Override + public HandlerFunction getHandler(SystemSetting.ThemeRouteRules routeRules, + String name) { + return request -> ServerResponse.ok() + .render(DefaultTemplateEnum.AUTHOR.getValue(), + Map.of("name", name, + "author", getByName(name), + "posts", postList(request, name), + ModelConst.TEMPLATE_ID, DefaultTemplateEnum.AUTHOR.getValue() + ) + ); + } + + private Mono> postList(ServerRequest request, String name) { + String path = request.path(); + return environmentFetcher.fetchPost() + .map(p -> defaultIfNull(p.getPostPageSize(), ModelConst.DEFAULT_PAGE_SIZE)) + .flatMap(pageSize -> postFinder.listByOwner(pageNum(request), pageSize, name)) + .map(list -> new UrlContextListResult.Builder() + .listResult(list) + .nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list))) + .prevUrl(PageUrlUtils.prevPageUrl(path)) + .build()); + } + + private Mono getByName(String name) { + return client.fetch(User.class, name) + .map(UserVo::from); + } + + @Override + public boolean supports(GroupVersionKind gvk) { + return GroupVersionKind.fromExtension(User.class).equals(gvk); + } +} diff --git a/src/test/java/run/halo/app/core/extension/reconciler/UserReconcilerTest.java b/src/test/java/run/halo/app/core/extension/reconciler/UserReconcilerTest.java new file mode 100644 index 0000000000..fc11fbe469 --- /dev/null +++ b/src/test/java/run/halo/app/core/extension/reconciler/UserReconcilerTest.java @@ -0,0 +1,81 @@ +package run.halo.app.core.extension.reconciler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Optional; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; +import run.halo.app.core.extension.User; +import run.halo.app.extension.ExtensionClient; +import run.halo.app.extension.Metadata; +import run.halo.app.extension.controller.Reconciler; +import run.halo.app.infra.AnonymousUserConst; +import run.halo.app.infra.ExternalUrlSupplier; +import run.halo.app.theme.router.PermalinkIndexUpdateCommand; + +/** + * Tests for {@link UserReconciler}. + * + * @author guqing + * @since 2.0.1 + */ +@ExtendWith(MockitoExtension.class) +class UserReconcilerTest { + @Mock + private ApplicationEventPublisher eventPublisher; + + @Mock + private ExternalUrlSupplier externalUrlSupplier; + + @Mock + private ExtensionClient client; + + @InjectMocks + private UserReconciler userReconciler; + + @Test + void permalinkForFakeUser() throws URISyntaxException { + when(externalUrlSupplier.get()).thenReturn(new URI("http://localhost:8090")); + + when(client.fetch(eq(User.class), eq("fake-user"))) + .thenReturn(Optional.of(user("fake-user"))); + userReconciler.reconcile(new Reconciler.Request("fake-user")); + verify(client, times(1)).update(any(User.class)); + verify(eventPublisher, times(1)).publishEvent(any(PermalinkIndexUpdateCommand.class)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(User.class); + verify(client, times(1)).update(captor.capture()); + assertThat(captor.getValue().getStatus().getPermalink()) + .isEqualTo("http://localhost:8090/authors/fake-user"); + } + + @Test + void permalinkForAnonymousUser() { + when(client.fetch(eq(User.class), eq(AnonymousUserConst.PRINCIPAL))) + .thenReturn(Optional.of(user(AnonymousUserConst.PRINCIPAL))); + userReconciler.reconcile(new Reconciler.Request(AnonymousUserConst.PRINCIPAL)); + verify(client, times(0)).update(any(User.class)); + verify(eventPublisher, times(0)).publishEvent(any(PermalinkIndexUpdateCommand.class)); + } + + User user(String name) { + User user = new User(); + user.setMetadata(new Metadata()); + user.getMetadata().setName(name); + user.getMetadata().setFinalizers(Set.of("user-protection")); + return user; + } +} \ No newline at end of file diff --git a/src/test/java/run/halo/app/theme/finders/vo/UserVoTest.java b/src/test/java/run/halo/app/theme/finders/vo/UserVoTest.java new file mode 100644 index 0000000000..34ac1a8e8d --- /dev/null +++ b/src/test/java/run/halo/app/theme/finders/vo/UserVoTest.java @@ -0,0 +1,84 @@ +package run.halo.app.theme.finders.vo; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Instant; +import java.util.List; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import run.halo.app.core.extension.User; +import run.halo.app.extension.Metadata; +import run.halo.app.infra.utils.JsonUtils; + +/** + * Tests for {@link UserVo}. + * + * @author guqing + * @since 2.0.1 + */ +class UserVoTest { + + @Test + void from() throws JSONException { + User user = new User(); + user.setMetadata(new Metadata()); + user.getMetadata().setName("fake-user"); + user.setSpec(new User.UserSpec()); + user.getSpec().setPassword("123456"); + user.getSpec().setEmail("example@example.com"); + user.getSpec().setAvatar("avatar"); + user.getSpec().setDisplayName("fake-user-display-name"); + user.getSpec().setBio("user bio"); + user.getSpec().setDisabled(false); + user.getSpec().setPhone("123456789"); + user.getSpec().setRegisteredAt(Instant.parse("2022-01-01T00:00:00.00Z")); + user.getSpec().setLoginHistoryLimit(5); + user.getSpec().setTwoFactorAuthEnabled(false); + + user.setStatus(new User.UserStatus()); + user.getStatus().setLastLoginAt(Instant.parse("2022-01-02T00:00:00.00Z")); + User.LoginHistory loginHistory = new User.LoginHistory(); + loginHistory.setLoginAt(Instant.parse("2022-01-02T00:00:00.00Z")); + loginHistory.setReason("login reason"); + loginHistory.setUserAgent("user agent"); + user.getStatus().setLoginHistories(List.of(loginHistory)); + + UserVo userVo = UserVo.from(user); + JSONAssert.assertEquals(""" + { + "metadata": { + "name": "fake-user" + }, + "spec": { + "displayName": "fake-user-display-name", + "avatar": "avatar", + "email": "example@example.com", + "phone": "123456789", + "password": "[PROTECTED]", + "bio": "user bio", + "registeredAt": "2022-01-01T00:00:00Z", + "twoFactorAuthEnabled": false, + "disabled": false, + "loginHistoryLimit": 5 + }, + "status": { + "loginHistories": [] + } + } + """, + JsonUtils.objectToJson(userVo), + true); + } + + @Test + void fromWhenStatusIsNull() { + User user = new User(); + user.setMetadata(new Metadata()); + user.getMetadata().setName("fake-user"); + user.setSpec(new User.UserSpec()); + UserVo userVo = UserVo.from(user); + + assertThat(userVo).isNotNull(); + } +} \ No newline at end of file diff --git a/src/test/java/run/halo/app/theme/router/strategy/AuthorRouteStrategyTest.java b/src/test/java/run/halo/app/theme/router/strategy/AuthorRouteStrategyTest.java new file mode 100644 index 0000000000..7426d85fc5 --- /dev/null +++ b/src/test/java/run/halo/app/theme/router/strategy/AuthorRouteStrategyTest.java @@ -0,0 +1,68 @@ +package run.halo.app.theme.router.strategy; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; +import run.halo.app.core.extension.User; +import run.halo.app.extension.Metadata; +import run.halo.app.extension.ReactiveExtensionClient; +import run.halo.app.theme.DefaultTemplateEnum; + +/** + * Tests for {@link AuthorRouteStrategy}. + * + * @author guqing + * @since 2.0.1 + */ +class AuthorRouteStrategyTest extends RouterStrategyTestSuite { + + @Mock + private ReactiveExtensionClient client; + + @InjectMocks + private AuthorRouteStrategy strategy; + + @Test + void handlerTest() { + User user = new User(); + Metadata metadata = new Metadata(); + metadata.setName("fake-user"); + user.setMetadata(metadata); + user.setSpec(new User.UserSpec()); + + when(client.fetch(eq(User.class), eq("fake-user"))).thenReturn(Mono.just(user)); + permalinkHttpGetRouter.insert("/authors/fake-user", + strategy.getHandler(getThemeRouteRules(), "fake-user")); + + when(viewResolver.resolveViewName(eq(DefaultTemplateEnum.AUTHOR.getValue()), any())) + .thenReturn(Mono.just(new EmptyView() { + @Override + public Mono render(Map model, MediaType contentType, + ServerWebExchange exchange) { + assertThat(model.get("name")).isEqualTo("fake-user"); + assertThat(model.get("_templateId")) + .isEqualTo(DefaultTemplateEnum.AUTHOR.getValue()); + assertThat(model.get("author")).isNotNull(); + assertThat(model.get("posts")).isNotNull(); + return Mono.empty(); + } + })); + + WebTestClient webTestClient = getWebTestClient(getRouterFunction()); + webTestClient.get() + .uri("/authors/fake-user") + .exchange() + .expectStatus() + .isOk(); + } +} \ No newline at end of file