From 510f155e050c5be4d6a23f60e0adc01933fe529e Mon Sep 17 00:00:00 2001 From: guqing <38999863+guqing@users.noreply.github.com> Date: Mon, 19 Sep 2022 10:22:12 +0800 Subject: [PATCH] feat: count the number of posts under categories and tags (#2402) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind feature /milestone 2.0 /area core #### What this PR does / why we need it: 统计分类和标签下的文章 #### Which issue(s) this PR fixes: Fixes #2401 #### Special notes for your reviewer: how to test it? 1. 创建一个多层级的分类及若干文章,查看分类的 status.posts 是否包含当前及其子分类下的文章 2. 创建标签,并将其分配给若干文章,查看标签的 status.posts 是否正确 3. 在主题端查看分类和标签包含的文章数量是否正确,需要注意的是主题端显示的文章数量只包含已发布且 visiable 为 public 且未删除的 /cc @halo-dev/sig-halo #### Does this PR introduce a user-facing change? ```release-note None ``` --- .../run/halo/app/core/extension/Category.java | 7 +- .../run/halo/app/core/extension/Post.java | 67 ++++++ .../java/run/halo/app/core/extension/Tag.java | 2 +- .../reconciler/CategoryReconciler.java | 105 +++++++- .../extension/reconciler/TagReconciler.java | 52 +++- .../app/theme/finders/vo/CategoryTreeVo.java | 17 ++ .../halo/app/theme/finders/vo/CategoryVo.java | 17 ++ .../run/halo/app/theme/finders/vo/TagVo.java | 18 ++ .../reconciler/CategoryReconcilerTest.java | 224 ++++++++++++++++++ .../reconciler/TagReconcilerTest.java | 56 ++++- .../theme/finders/impl/TagFinderImplTest.java | 7 +- 11 files changed, 546 insertions(+), 26 deletions(-) create mode 100644 src/test/java/run/halo/app/core/extension/reconciler/CategoryReconcilerTest.java diff --git a/src/main/java/run/halo/app/core/extension/Category.java b/src/main/java/run/halo/app/core/extension/Category.java index fc3a7a895..bec7b4583 100644 --- a/src/main/java/run/halo/app/core/extension/Category.java +++ b/src/main/java/run/halo/app/core/extension/Category.java @@ -27,6 +27,11 @@ public class Category extends AbstractExtension { @Schema private CategoryStatus status; + @JsonIgnore + public boolean isDeleted() { + return getMetadata().getDeletionTimestamp() != null; + } + @Data public static class CategorySpec { @@ -64,6 +69,6 @@ public class Category extends AbstractExtension { /** * 包括当前和其下所有层级的文章 name (depth=max). */ - private List posts; + private List posts; } } diff --git a/src/main/java/run/halo/app/core/extension/Post.java b/src/main/java/run/halo/app/core/extension/Post.java index dc5e8ebcb..5214eaf0d 100644 --- a/src/main/java/run/halo/app/core/extension/Post.java +++ b/src/main/java/run/halo/app/core/extension/Post.java @@ -6,6 +6,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; @@ -48,6 +49,17 @@ public class Post extends AbstractExtension { return status; } + @JsonIgnore + public boolean isDeleted() { + return Objects.equals(true, spec.getDeleted()) + || getMetadata().getDeletionTimestamp() != null; + } + + @JsonIgnore + public boolean isPublished() { + return Objects.equals(true, spec.getPublished()); + } + @Data public static class PostSpec { @Schema(required = true, minLength = 1) @@ -149,4 +161,59 @@ public class Post extends AbstractExtension { INTERNAL, PRIVATE } + + @Data + public static class CompactPost { + private String name; + + private VisibleEnum visible; + + private Boolean published; + + public static Builder builder() { + return new Builder(); + } + + /** + *

Compact post builder.

+ *

Can not replace with lombok builder.

+ *

The class used by subclasses of {@link AbstractExtension} must have a no-args + * constructor.

+ */ + public static class Builder { + private String name; + + private VisibleEnum visible; + + private Boolean published; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder visible(VisibleEnum visible) { + this.visible = visible; + return this; + } + + public Builder published(Boolean published) { + this.published = published; + return this; + } + + /** + * Build compact post. + * + * @return a compact post + */ + public CompactPost build() { + CompactPost compactPost = new CompactPost(); + compactPost.setName(name); + compactPost.setVisible(visible); + compactPost.setPublished(published); + return compactPost; + } + } + } } diff --git a/src/main/java/run/halo/app/core/extension/Tag.java b/src/main/java/run/halo/app/core/extension/Tag.java index 74768c9f1..bafc14f65 100644 --- a/src/main/java/run/halo/app/core/extension/Tag.java +++ b/src/main/java/run/halo/app/core/extension/Tag.java @@ -69,6 +69,6 @@ public class Tag extends AbstractExtension { private String permalink; - private List posts; + private List posts; } } diff --git a/src/main/java/run/halo/app/core/extension/reconciler/CategoryReconciler.java b/src/main/java/run/halo/app/core/extension/reconciler/CategoryReconciler.java index e9e776891..4d5bc5966 100644 --- a/src/main/java/run/halo/app/core/extension/reconciler/CategoryReconciler.java +++ b/src/main/java/run/halo/app/core/extension/reconciler/CategoryReconciler.java @@ -1,9 +1,19 @@ package run.halo.app.core.extension.reconciler; +import java.time.Duration; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.springframework.lang.Nullable; import run.halo.app.content.permalinks.CategoryPermalinkPolicy; import run.halo.app.core.extension.Category; +import run.halo.app.core.extension.Post; import run.halo.app.extension.ExtensionClient; import run.halo.app.extension.controller.Reconciler; import run.halo.app.infra.utils.JsonUtils; @@ -27,17 +37,20 @@ public class CategoryReconciler implements Reconciler { @Override public Result reconcile(Request request) { - client.fetch(Category.class, request.name()) - .ifPresent(category -> { - if (isDeleted(category)) { + return client.fetch(Category.class, request.name()) + .map(category -> { + if (category.isDeleted()) { cleanUpResourcesAndRemoveFinalizer(request.name()); - return; + return new Result(false, null); } addFinalizerIfNecessary(category); - reconcileStatus(request.name()); - }); - return new Result(false, null); + reconcileStatusPermalink(request.name()); + + reconcileStatusPosts(request.name()); + return new Result(true, Duration.ofMinutes(1)); + }) + .orElseGet(() -> new Result(false, null)); } private void addFinalizerIfNecessary(Category oldCategory) { @@ -72,7 +85,7 @@ public class CategoryReconciler implements Reconciler { }); } - private void reconcileStatus(String name) { + private void reconcileStatusPermalink(String name) { client.fetch(Category.class, name) .ifPresent(category -> { Category oldCategory = JsonUtils.deepCopy(category); @@ -88,7 +101,79 @@ public class CategoryReconciler implements Reconciler { }); } - private boolean isDeleted(Category category) { - return category.getMetadata().getDeletionTimestamp() != null; + private void reconcileStatusPosts(String name) { + client.fetch(Category.class, name).ifPresent(category -> { + Category oldCategory = JsonUtils.deepCopy(category); + + populatePosts(category); + + if (!oldCategory.equals(category)) { + client.update(category); + } + }); + } + + private void populatePosts(Category category) { + String name = category.getMetadata().getName(); + List categoryNames = listChildrenByName(name) + .stream() + .map(item -> item.getMetadata().getName()) + .toList(); + + List posts = client.list(Post.class, post -> !post.isDeleted(), null); + + // populate post to status + List compactPosts = posts.stream() + .filter(post -> includes(post.getSpec().getCategories(), categoryNames)) + .map(post -> Post.CompactPost.builder() + .name(post.getMetadata().getName()) + .visible(post.getSpec().getVisible()) + .published(post.isPublished()) + .build()) + .toList(); + category.getStatusOrDefault().setPosts(compactPosts); + } + + /** + * whether {@code categoryRefs} contains elements in {@code categoryNames}. + * + * @param categoryRefs category left to judge + * @param categoryNames category right to judge + * @return true if {@code categoryRefs} contains elements in {@code categoryNames}. + */ + private boolean includes(@Nullable List categoryRefs, List categoryNames) { + if (categoryRefs == null || categoryNames == null) { + return false; + } + for (String categoryRef : categoryRefs) { + if (categoryNames.contains(categoryRef)) { + return true; + } + } + return false; + } + + private List listChildrenByName(String name) { + List categories = client.list(Category.class, null, null); + Map nameIdentityMap = categories.stream() + .collect(Collectors.toMap(category -> category.getMetadata().getName(), + Function.identity())); + final List children = new ArrayList<>(); + + Deque deque = new ArrayDeque<>(); + deque.add(name); + while (!deque.isEmpty()) { + String itemName = deque.poll(); + Category category = nameIdentityMap.get(itemName); + if (category == null) { + continue; + } + children.add(category); + List childrenNames = category.getSpec().getChildren(); + if (childrenNames != null) { + deque.addAll(childrenNames); + } + } + return children; } } diff --git a/src/main/java/run/halo/app/core/extension/reconciler/TagReconciler.java b/src/main/java/run/halo/app/core/extension/reconciler/TagReconciler.java index fae578f6d..c4ccbc932 100644 --- a/src/main/java/run/halo/app/core/extension/reconciler/TagReconciler.java +++ b/src/main/java/run/halo/app/core/extension/reconciler/TagReconciler.java @@ -1,8 +1,11 @@ package run.halo.app.core.extension.reconciler; +import java.time.Duration; import java.util.HashSet; +import java.util.List; import java.util.Set; import run.halo.app.content.permalinks.TagPermalinkPolicy; +import run.halo.app.core.extension.Post; import run.halo.app.core.extension.Tag; import run.halo.app.extension.ExtensionClient; import run.halo.app.extension.controller.Reconciler; @@ -26,17 +29,20 @@ public class TagReconciler implements Reconciler { @Override public Result reconcile(Request request) { - client.fetch(Tag.class, request.name()) - .ifPresent(tag -> { + return client.fetch(Tag.class, request.name()) + .map(tag -> { if (isDeleted(tag)) { cleanUpResourcesAndRemoveFinalizer(request.name()); - return; + return new Result(false, null); } addFinalizerIfNecessary(tag); - this.reconcileStatus(request.name()); - }); - return new Result(false, null); + this.reconcileStatusPermalink(request.name()); + + reconcileStatusPosts(request.name()); + return new Result(true, Duration.ofMinutes(1)); + }) + .orElseGet(() -> new Result(false, null)); } private void cleanUpResources(Tag tag) { @@ -71,7 +77,7 @@ public class TagReconciler implements Reconciler { }); } - private void reconcileStatus(String tagName) { + private void reconcileStatusPermalink(String tagName) { client.fetch(Tag.class, tagName) .ifPresent(tag -> { Tag oldTag = JsonUtils.deepCopy(tag); @@ -87,6 +93,38 @@ public class TagReconciler implements Reconciler { }); } + private void reconcileStatusPosts(String tagName) { + client.fetch(Tag.class, tagName).ifPresent(tag -> { + Tag oldTag = JsonUtils.deepCopy(tag); + + populatePosts(tag); + + if (!oldTag.equals(tag)) { + client.update(tag); + } + }); + } + + private void populatePosts(Tag tag) { + List compactPosts = client.list(Post.class, null, null) + .stream() + .filter(post -> includes(post.getSpec().getTags(), tag.getMetadata().getName())) + .map(post -> Post.CompactPost.builder() + .name(post.getMetadata().getName()) + .published(post.isPublished()) + .visible(post.getSpec().getVisible()) + .build()) + .toList(); + tag.getStatusOrDefault().setPosts(compactPosts); + } + + private boolean includes(List tags, String tagName) { + if (tags == null) { + return false; + } + return tags.contains(tagName); + } + private boolean isDeleted(Tag tag) { return tag.getMetadata().getDeletionTimestamp() != null; } diff --git a/src/main/java/run/halo/app/theme/finders/vo/CategoryTreeVo.java b/src/main/java/run/halo/app/theme/finders/vo/CategoryTreeVo.java index 640994ec4..4276992ec 100644 --- a/src/main/java/run/halo/app/theme/finders/vo/CategoryTreeVo.java +++ b/src/main/java/run/halo/app/theme/finders/vo/CategoryTreeVo.java @@ -1,12 +1,14 @@ package run.halo.app.theme.finders.vo; import java.util.List; +import java.util.Objects; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; import org.springframework.util.Assert; import run.halo.app.core.extension.Category; +import run.halo.app.core.extension.Post; import run.halo.app.extension.MetadataOperator; /** @@ -46,4 +48,19 @@ public class CategoryTreeVo { .children(List.of()) .build(); } + + /** + * Gets the number of posts under the current category and its sub categories. + * + * @return the number of posts + */ + public long postCount() { + if (this.status == null || this.status.getPosts() == null) { + return 0; + } + return this.status.getPosts().stream() + .filter(post -> Objects.equals(true, post.getPublished()) + && Post.VisibleEnum.PUBLIC.equals(post.getVisible())) + .count(); + } } diff --git a/src/main/java/run/halo/app/theme/finders/vo/CategoryVo.java b/src/main/java/run/halo/app/theme/finders/vo/CategoryVo.java index 302b09335..0aa77d572 100644 --- a/src/main/java/run/halo/app/theme/finders/vo/CategoryVo.java +++ b/src/main/java/run/halo/app/theme/finders/vo/CategoryVo.java @@ -1,9 +1,11 @@ package run.halo.app.theme.finders.vo; +import java.util.Objects; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Value; import run.halo.app.core.extension.Category; +import run.halo.app.core.extension.Post; import run.halo.app.extension.MetadataOperator; /** @@ -36,4 +38,19 @@ public class CategoryVo { .status(category.getStatus()) .build(); } + + /** + * Gets the number of posts under the current category and its sub categories. + * + * @return the number of posts + */ + public long postCount() { + if (this.status == null || this.status.getPosts() == null) { + return 0; + } + return this.status.getPosts().stream() + .filter(post -> Objects.equals(true, post.getPublished()) + && Post.VisibleEnum.PUBLIC.equals(post.getVisible())) + .count(); + } } diff --git a/src/main/java/run/halo/app/theme/finders/vo/TagVo.java b/src/main/java/run/halo/app/theme/finders/vo/TagVo.java index bff17faaa..ce1835680 100644 --- a/src/main/java/run/halo/app/theme/finders/vo/TagVo.java +++ b/src/main/java/run/halo/app/theme/finders/vo/TagVo.java @@ -1,7 +1,9 @@ package run.halo.app.theme.finders.vo; +import java.util.Objects; import lombok.Builder; import lombok.Value; +import run.halo.app.core.extension.Post; import run.halo.app.core.extension.Tag; import run.halo.app.extension.MetadataOperator; @@ -33,4 +35,20 @@ public class TagVo { .status(status) .build(); } + + /** + * Gets the number of posts under the current tag. + * + * @return the number of posts + */ + public long postCount() { + if (this.status == null || this.status.getPosts() == null) { + return 0; + } + return this.status.getPosts() + .stream() + .filter(post -> Objects.equals(true, post.getPublished()) + && Post.VisibleEnum.PUBLIC.equals(post.getVisible())) + .count(); + } } diff --git a/src/test/java/run/halo/app/core/extension/reconciler/CategoryReconcilerTest.java b/src/test/java/run/halo/app/core/extension/reconciler/CategoryReconcilerTest.java new file mode 100644 index 000000000..f562e465d --- /dev/null +++ b/src/test/java/run/halo/app/core/extension/reconciler/CategoryReconcilerTest.java @@ -0,0 +1,224 @@ +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.lenient; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.time.Duration; +import java.util.List; +import java.util.Optional; +import org.json.JSONException; +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.skyscreamer.jsonassert.JSONAssert; +import run.halo.app.content.TestPost; +import run.halo.app.content.permalinks.CategoryPermalinkPolicy; +import run.halo.app.core.extension.Category; +import run.halo.app.core.extension.Post; +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; + +/** + * Tests for {@link CategoryReconciler}. + * + * @author guqing + * @since 2.0.0 + */ +@ExtendWith(MockitoExtension.class) +class CategoryReconcilerTest { + @Mock + private ExtensionClient client; + + @Mock + private CategoryPermalinkPolicy categoryPermalinkPolicy; + + @InjectMocks + private CategoryReconciler categoryReconciler; + + @Test + void reconcileStatusPostForCategoryA() throws JSONException { + reconcileStatusPostPilling("category-A"); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Category.class); + verify(client, times(2)).update(captor.capture()); + JSONAssert.assertEquals(""" + [ + { + "name": "post-1", + "visible": "PUBLIC", + "published": false + }, + { + "name": "post-2", + "visible": "PUBLIC", + "published": false + }, + { + "name": "post-3", + "visible": "PUBLIC", + "published": false + }, + { + "name": "post-4", + "visible": "PUBLIC", + "published": false + } + ] + """, + JsonUtils.objectToJson(captor.getAllValues().get(1).getStatusOrDefault().getPosts()), + true); + } + + @Test + void reconcileStatusPostForCategoryB() throws JSONException { + reconcileStatusPostPilling("category-B"); + ArgumentCaptor captor = ArgumentCaptor.forClass(Category.class); + verify(client, times(2)).update(captor.capture()); + JSONAssert.assertEquals(""" + [ + { + "name": "post-1", + "visible": "PUBLIC", + "published": false + }, + { + "name": "post-2", + "visible": "PUBLIC", + "published": false + }, + { + "name": "post-3", + "visible": "PUBLIC", + "published": false + } + ] + """, + JsonUtils.objectToJson(captor.getAllValues().get(1).getStatusOrDefault().getPosts()), + true); + } + + @Test + void reconcileStatusPostForCategoryC() throws JSONException { + reconcileStatusPostPilling("category-C"); + ArgumentCaptor captor = ArgumentCaptor.forClass(Category.class); + verify(client, times(2)).update(captor.capture()); + JSONAssert.assertEquals(""" + [ + { + "name": "post-1", + "visible": "PUBLIC", + "published": false + }, + { + "name": "post-2", + "visible": "PUBLIC", + "published": false + } + ] + """, + JsonUtils.objectToJson(captor.getAllValues().get(1).getStatusOrDefault().getPosts()), + true); + } + + @Test + void reconcileStatusPostForCategoryD() throws JSONException { + reconcileStatusPostPilling("category-D"); + ArgumentCaptor captor = ArgumentCaptor.forClass(Category.class); + verify(client, times(2)).update(captor.capture()); + JSONAssert.assertEquals(""" + [ + { + "name": "post-1", + "visible": "PUBLIC", + "published": false + } + ] + """, + JsonUtils.objectToJson(captor.getAllValues().get(1).getStatusOrDefault().getPosts()), + true); + } + + + private void reconcileStatusPostPilling(String reconcileCategoryName) { + categories().forEach(category -> { + lenient().when(client.fetch(eq(Category.class), eq(category.getMetadata().getName()))) + .thenReturn(Optional.of(category)); + }); + + lenient().when(client.list(eq(Post.class), any(), any())) + .thenReturn(posts()); + lenient().when(client.list(eq(Category.class), any(), any())) + .thenReturn(categories()); + + Reconciler.Result result = + categoryReconciler.reconcile(new Reconciler.Request(reconcileCategoryName)); + assertThat(result.reEnqueue()).isTrue(); + assertThat(result.retryAfter()).isEqualTo(Duration.ofMinutes(1)); + } + + private List categories() { + /* + * |-A(post-4) + * |-B(post-3) + * |-|-C(post-2,post-1) + * |-D(post-1) + */ + Category categoryA = category("category-A"); + categoryA.getSpec().setChildren(List.of("category-B", "category-D")); + + Category categoryB = category("category-B"); + categoryB.getSpec().setChildren(List.of("category-C")); + + Category categoryC = category("category-C"); + Category categoryD = category("category-D"); + return List.of(categoryA, categoryB, categoryC, categoryD); + } + + private Category category(String name) { + Category category = new Category(); + Metadata metadata = new Metadata(); + metadata.setName(name); + category.setMetadata(metadata); + category.setSpec(new Category.CategorySpec()); + category.setStatus(new Category.CategoryStatus()); + return category; + } + + private List posts() { + /* + * |-A(post-4) + * |-B(post-3) + * |-|-C(post-2,post-1) + * |-D(post-1) + */ + Post post1 = TestPost.postV1(); + post1.getMetadata().setName("post-1"); + post1.getSpec().setCategories(List.of("category-D", "category-C")); + post1.getSpec().setVisible(Post.VisibleEnum.PUBLIC); + + Post post2 = TestPost.postV1(); + post2.getMetadata().setName("post-2"); + post2.getSpec().setCategories(List.of("category-C")); + post2.getSpec().setVisible(Post.VisibleEnum.PUBLIC); + + Post post3 = TestPost.postV1(); + post3.getMetadata().setName("post-3"); + post3.getSpec().setCategories(List.of("category-B")); + post3.getSpec().setVisible(Post.VisibleEnum.PUBLIC); + + Post post4 = TestPost.postV1(); + post4.getMetadata().setName("post-4"); + post4.getSpec().setCategories(List.of("category-A")); + post4.getSpec().setVisible(Post.VisibleEnum.PUBLIC); + return List.of(post1, post2, post3, post4); + } +} \ No newline at end of file diff --git a/src/test/java/run/halo/app/core/extension/reconciler/TagReconcilerTest.java b/src/test/java/run/halo/app/core/extension/reconciler/TagReconcilerTest.java index e4184b830..018d7ccb6 100644 --- a/src/test/java/run/halo/app/core/extension/reconciler/TagReconcilerTest.java +++ b/src/test/java/run/halo/app/core/extension/reconciler/TagReconcilerTest.java @@ -8,17 +8,23 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.time.Instant; +import java.util.List; import java.util.Optional; +import org.json.JSONException; 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.skyscreamer.jsonassert.JSONAssert; +import run.halo.app.content.TestPost; import run.halo.app.content.permalinks.TagPermalinkPolicy; +import run.halo.app.core.extension.Post; import run.halo.app.core.extension.Tag; import run.halo.app.extension.ExtensionClient; import run.halo.app.extension.Metadata; +import run.halo.app.infra.utils.JsonUtils; /** * Tests for {@link TagReconciler}. @@ -48,7 +54,7 @@ class TagReconcilerTest { tagReconciler.reconcile(new TagReconciler.Request("fake-tag")); - verify(client, times(2)).update(captor.capture()); + verify(client, times(3)).update(captor.capture()); verify(tagPermalinkPolicy, times(1)).onPermalinkAdd(any()); verify(tagPermalinkPolicy, times(1)).onPermalinkDelete(any()); Tag capture = captor.getValue(); @@ -57,7 +63,7 @@ class TagReconcilerTest { // change slug tag.getSpec().setSlug("new-slug"); tagReconciler.reconcile(new TagReconciler.Request("fake-tag")); - verify(client, times(3)).update(captor.capture()); + verify(client, times(4)).update(captor.capture()); verify(tagPermalinkPolicy, times(2)).onPermalinkAdd(any()); verify(tagPermalinkPolicy, times(2)).onPermalinkDelete(any()); assertThat(capture.getStatus().getPermalink()).isEqualTo("/tags/new-slug"); @@ -78,6 +84,33 @@ class TagReconcilerTest { verify(tagPermalinkPolicy, times(0)).permalink(any()); } + @Test + void reconcileStatusPosts() throws JSONException { + Tag tag = tag(); + when(client.fetch(eq(Tag.class), eq("fake-tag"))) + .thenReturn(Optional.of(tag)); + when(client.list(eq(Post.class), any(), any())).thenReturn(posts()); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Tag.class); + tagReconciler.reconcile(new TagReconciler.Request("fake-tag")); + verify(client, times(2)).update(captor.capture()); + List allValues = captor.getAllValues(); + List posts = allValues.get(1).getStatusOrDefault().getPosts(); + JSONAssert.assertEquals(""" + [{ + "name": "fake-post-1", + "published": false, + "visible": "PUBLIC" + }, + { + "name": "fake-post-3", + "published": false, + "visible": "PRIVATE" + }] + """, + JsonUtils.objectToJson(posts), true); + } + Tag tag() { Tag tag = new Tag(); tag.setMetadata(new Metadata()); @@ -89,4 +122,23 @@ class TagReconcilerTest { tag.setStatus(new Tag.TagStatus()); return tag; } + + private List posts() { + Post post1 = TestPost.postV1(); + post1.getMetadata().setName("fake-post-1"); + post1.getSpec().setVisible(Post.VisibleEnum.PUBLIC); + post1.getSpec().setTags(List.of("fake-tag", "tag-A", "tag-B")); + + Post post2 = TestPost.postV1(); + post2.getMetadata().setName("fake-post-2"); + post2.getSpec().setVisible(Post.VisibleEnum.INTERNAL); + post2.getSpec().setTags(List.of("tag-A", "tag-C")); + + Post post3 = TestPost.postV1(); + post3.getMetadata().setName("fake-post-3"); + post3.getSpec().setVisible(Post.VisibleEnum.PRIVATE); + post3.getSpec().setTags(List.of("tag-A", "fake-tag")); + return List.of(post1, post2, post3); + } + } \ No newline at end of file diff --git a/src/test/java/run/halo/app/theme/finders/impl/TagFinderImplTest.java b/src/test/java/run/halo/app/theme/finders/impl/TagFinderImplTest.java index 93921543f..00cb49857 100644 --- a/src/test/java/run/halo/app/theme/finders/impl/TagFinderImplTest.java +++ b/src/test/java/run/halo/app/theme/finders/impl/TagFinderImplTest.java @@ -65,10 +65,7 @@ class TagFinderImplTest { }, "status": { "permalink": "permalink-1", - "posts": [ - "p1", - "p2" - ] + "posts": [] } } """, @@ -120,7 +117,7 @@ class TagFinderImplTest { Tag.TagStatus tagStatus = new Tag.TagStatus(); tagStatus.setPermalink("permalink-" + i); - tagStatus.setPosts(List.of("p1", "p2")); + tagStatus.setPosts(List.of()); tag.setStatus(tagStatus); return tag; }