Merge "correct notification, add community" into main
diff --git a/src/main/java/com/g9/g9backend/controller/CommunityController.java b/src/main/java/com/g9/g9backend/controller/CommunityController.java
index 229fe96..ea79dbb 100644
--- a/src/main/java/com/g9/g9backend/controller/CommunityController.java
+++ b/src/main/java/com/g9/g9backend/controller/CommunityController.java
@@ -1,8 +1,25 @@
 package com.g9.g9backend.controller;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.g9.g9backend.pojo.Community;
+import com.g9.g9backend.pojo.DTO.*;
+import com.g9.g9backend.pojo.Subscription;
+import com.g9.g9backend.pojo.Thread;
+import com.g9.g9backend.pojo.ThreadLike;
+import com.g9.g9backend.service.*;
+import org.jetbrains.annotations.NotNull;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
 
 /**
  * CommunityController 社区控制器类,处理与社区相关的请求
@@ -12,5 +29,250 @@
 @RestController
 public class CommunityController {
 
+    private final CommunityService communityService;
+
+    private final ThreadService threadService;
+
+    private final SubscriptionService subscriptionService;
+
+    private final ThreadLikeService threadLikeService;
+
     private final Logger logger = LoggerFactory.getLogger(CommunityController.class);
+
+    public CommunityController(CommunityService communityService, ThreadService threadService, SubscriptionService subscriptionService, ThreadLikeService threadLikeService) {
+        this.communityService = communityService;
+        this.threadService = threadService;
+        this.threadLikeService = threadLikeService;
+        this.subscriptionService = subscriptionService;
+    }
+
+    @PostMapping(value = "/thread")
+    public ResponseEntity<String> postThread(@RequestBody Thread thread) {
+        thread.setLikes(0);
+        thread.setCommentNumber(0);
+        threadService.save(thread);
+
+        Community community = communityService.getById(thread.getCommunityId());
+        community.setThreadNumber(community.getThreadNumber() + 1);
+        communityService.updateById(community);
+
+        logger.info("帖子已发布");
+
+        return ResponseEntity.ok("");
+    }
+
+    @PostMapping(value = "/thread/like")
+    public ResponseEntity<String> postThreadLike(@RequestBody Map<String, Integer> request) {
+        Integer userId = request.get("userId");
+        Integer threadId = request.get("threadId");
+
+        ThreadLike threadLike = new ThreadLike(userId, threadId);
+        threadLikeService.save(threadLike);
+
+        Thread thread = threadService.getById(threadId);
+        thread.setLikes(thread.getLikes() + 1);
+        threadService.updateById(thread);
+
+        logger.info("点赞成功");
+
+        return ResponseEntity.ok("");
+    }
+
+    @DeleteMapping(value = "/thread")
+    public ResponseEntity<String> deleteThread(@RequestParam Integer threadId) {
+        Thread thread = threadService.getById(threadId);
+        threadService.removeById(threadId);
+
+        Community community = communityService.getById(thread.getCommunityId());
+        community.setThreadNumber(community.getThreadNumber() - 1);
+        communityService.updateById(community);
+
+        logger.info("帖子已删除");
+
+        return ResponseEntity.noContent().build();
+    }
+
+    @DeleteMapping(value = "/thread/like")
+    public ResponseEntity<String> deleteLike(@RequestParam Integer userId, @RequestParam Integer threadId) {
+        LambdaQueryWrapper<ThreadLike> threadLikeQuery = new LambdaQueryWrapper<ThreadLike>()
+                .eq(ThreadLike::getUserId, userId)
+                .eq(ThreadLike::getThreadId, threadId);
+
+        threadLikeService.remove(threadLikeQuery);
+
+        Thread thread = threadService.getById(threadId);
+        thread.setLikes(thread.getLikes() - 1);
+        threadService.updateById(thread);
+
+        logger.info("取消点赞成功");
+
+        return ResponseEntity.noContent().build();
+    }
+
+    @GetMapping(value = "/community")
+    public ResponseEntity<GetCommunityDTO> getCommunity(@RequestParam String searchValue,
+                                                        @RequestParam String type,
+                                                        @RequestParam Integer pageNumber,
+                                                        @RequestParam Integer rows) {
+        Page<Community> communityPage = new Page<>(pageNumber, rows);
+        LambdaQueryWrapper<Community> communityQuery = new LambdaQueryWrapper<Community>()
+                .eq(Community::getType, type)
+                .like(StringUtils.isNotBlank(searchValue), Community::getCommunityName, searchValue)
+                .orderByDesc(Community::getHot);
+
+        Page<Community> result = communityService.page(communityPage, communityQuery);
+
+        GetCommunityDTO getCommunityDTO = wrapCommunityPage(result, item -> {
+            GetCommunityDTO.Community community = new GetCommunityDTO.Community();
+            community.setCommunityId(item.getCommunityId());
+            community.setCommunityName(item.getCommunityName());
+            community.setCommunityPicture(item.getCommunityPicture());
+            community.setDescription(item.getDescription());
+            community.setHot(item.getHot());
+            community.setThreadNumber(item.getThreadNumber());
+            community.setResourceId(item.getResourceId());
+            return community;
+        });
+
+        return ResponseEntity.ok(getCommunityDTO);
+    }
+
+    @NotNull
+    private <T> GetCommunityDTO wrapCommunityPage(Page<T> page, Function<T, GetCommunityDTO.Community> mapper) {
+        List<GetCommunityDTO.Community> records = page.getRecords().stream().map(mapper).toList();
+
+        return new GetCommunityDTO(records, (int) page.getTotal(), (int) page.getPages(), (int) page.getCurrent(), (int) page.getSize());
+    }
+
+    @GetMapping(value = "/community/info")
+    public ResponseEntity<Community> getCommunityInfo(@RequestParam Integer communityId) {
+
+        return ResponseEntity.ok(communityService.getById(communityId));
+    }
+
+    @GetMapping(value = "/thread")
+    public ResponseEntity<ThreadDTO> getThread(@RequestParam Integer threadId, @RequestParam Integer userId) {
+        LambdaQueryWrapper<ThreadLike> threadLikeQuery = new LambdaQueryWrapper<ThreadLike>()
+                .eq(ThreadLike::getUserId, userId)
+                .eq(ThreadLike::getThreadId, threadId);
+        Thread thread = threadService.getById(threadId);
+        ThreadDTO threadDTO = new ThreadDTO(
+                threadId,
+                thread.getUserId(),
+                thread.getThreadPicture(),
+                thread.getTitle(),
+                thread.getContent(),
+                thread.getLikes(),
+                threadLikeService.getOne(threadLikeQuery) != null,
+                thread.getCreateAt(),
+                thread.getCommentNumber(),
+                thread.getCommunityId()
+        );
+
+        return ResponseEntity.ok(threadDTO);
+    }
+
+    @GetMapping(value = "/community/threads")
+    public ResponseEntity<GetCommunityThreadsDTO> getCommunityThreads(@RequestParam Integer communityId,
+                                                                      @RequestParam Integer pageNumber,
+                                                                      @RequestParam Integer rows,
+                                                                      @RequestParam String option,
+                                                                      @RequestParam String searchValue,
+                                                                      @RequestParam Integer userId) {
+        Page<Thread> threadPage = new Page<>(pageNumber, rows);
+        LambdaQueryWrapper<Thread> threadQuery = new LambdaQueryWrapper<Thread>()
+                .eq(Thread::getCommunityId, communityId)
+                .like(StringUtils.isNotBlank(searchValue), Thread::getTitle, searchValue);
+
+        if (Objects.equals(option, "最高热度")) {
+
+            threadQuery.orderByDesc(Thread::getLikes);
+        } else {
+
+            List<Integer> userIds = subscriptionService.list(new LambdaQueryWrapper<Subscription>()
+                            .eq(Subscription::getUserId, userId))
+                            .stream()
+                            .map(Subscription::getFollowerId)
+                            .toList();
+
+            threadQuery.in(!userIds.isEmpty(), Thread::getUserId, userIds)
+                    .orderByDesc(Thread::getLikes);
+        }
+
+        Page<Thread> result = threadService.page(threadPage, threadQuery);
+
+        GetCommunityThreadsDTO getCommunityThreadsDTO = wrapThreadPage(result, item -> {
+            GetCommunityThreadsDTO.Thread thread = new GetCommunityThreadsDTO.Thread();
+            thread.setThreadId(item.getThreadId());
+            thread.setUserId(item.getUserId());
+            thread.setThreadPicture(item.getThreadPicture());
+            thread.setTitle(item.getTitle());
+            thread.setLikes(item.getLikes());
+            thread.setCreateAt(item.getCreateAt());
+            return thread;
+        });
+
+        return ResponseEntity.ok(getCommunityThreadsDTO);
+    }
+
+    @NotNull
+    private <T> GetCommunityThreadsDTO wrapThreadPage(Page<T> page, Function<T, GetCommunityThreadsDTO.Thread> mapper) {
+        List<GetCommunityThreadsDTO.Thread> records = page.getRecords().stream().map(mapper).toList();
+
+        return new GetCommunityThreadsDTO(records, (int) page.getTotal(), (int) page.getPages(), (int) page.getCurrent(), (int) page.getSize());
+    }
+
+    @GetMapping(value = "/community/hot")
+    public ResponseEntity<GetCommunityHotDTO> getCommunityHot() {
+        Page<Community> communityPage = new Page<>(1, 3);
+        LambdaQueryWrapper<Community> communityQuery = new LambdaQueryWrapper<Community>()
+                .orderByDesc(Community::getHot);
+
+        Page<Community> result = communityService.page(communityPage, communityQuery);
+
+        List<GetCommunityHotDTO.Community> communityList = getCommunityList(result);
+
+        GetCommunityHotDTO getCommunityHotDTO = new GetCommunityHotDTO(communityList);
+
+        return ResponseEntity.ok(getCommunityHotDTO);
+    }
+
+    @NotNull
+    private static List<GetCommunityHotDTO.Community> getCommunityList(Page<Community> communityPage) {
+        List<Community> communityListT = communityPage.getRecords();
+        List<GetCommunityHotDTO.Community> communityList = new ArrayList<>();
+        int status = 1;
+        for (Community community : communityListT) {
+            GetCommunityHotDTO.Community communityHot = new GetCommunityHotDTO.Community(
+                    community.getCommunityId(),
+                    community.getCommunityName(),
+                    community.getCommunityPicture(),
+                    community.getCommunityPicture(),
+                    community.getHot(),
+                    community.getType(),
+                    community.getThreadNumber(),
+                    community.getResourceId(),
+                    status);
+            communityList.add(communityHot);
+            status++;
+        }
+
+        return communityList;
+    }
+
+    @GetMapping(value = "/community/common")
+    public ResponseEntity<GetCommunityCommonDTO> getCommunityCommon() {
+        Page<Community> communityPage = new Page<>(2, 3);
+        LambdaQueryWrapper<Community> communityQuery = new LambdaQueryWrapper<Community>()
+                .orderByDesc(Community::getHot);
+
+        Page<Community> result = communityService.page(communityPage, communityQuery);
+
+        List<Community> communityList = result.getRecords();
+
+        GetCommunityCommonDTO getCommunityCommonDTO = new GetCommunityCommonDTO(communityList);
+
+        return ResponseEntity.ok(getCommunityCommonDTO);
+    }
+
 }
diff --git a/src/main/java/com/g9/g9backend/controller/NotificationController.java b/src/main/java/com/g9/g9backend/controller/NotificationController.java
index 55a30f3..4f451df 100644
--- a/src/main/java/com/g9/g9backend/controller/NotificationController.java
+++ b/src/main/java/com/g9/g9backend/controller/NotificationController.java
@@ -51,12 +51,12 @@
     public ResponseEntity<IPage<Notification>> getNotification(@RequestParam Integer userId,
                                                                @RequestParam Integer pageNumber,
                                                                @RequestParam Integer rows) {
-        Page<Notification> pageNotification = new Page<>(pageNumber, rows);
-        LambdaQueryWrapper<Notification> query = new LambdaQueryWrapper<Notification>()
+        Page<Notification> notificationPage = new Page<>(pageNumber, rows);
+        LambdaQueryWrapper<Notification> notificationQuery = new LambdaQueryWrapper<Notification>()
                 .eq(Notification::getUserId, userId)
                 .orderByDesc(Notification::getCreateAt);
 
-        IPage<Notification> result = notificationService.page(pageNotification, query);
+        IPage<Notification> result = notificationService.page(notificationPage, notificationQuery);
         logger.info("通知已返回");
 
         return ResponseEntity.ok(result);
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityCommonDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityCommonDTO.java
new file mode 100644
index 0000000..8e24f40
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityCommonDTO.java
@@ -0,0 +1,17 @@
+package com.g9.g9backend.pojo.DTO;
+
+import com.g9.g9backend.pojo.Community;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class GetCommunityCommonDTO {
+
+    List<Community> communityList;
+
+}
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityDTO.java
new file mode 100644
index 0000000..791bb10
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityDTO.java
@@ -0,0 +1,45 @@
+package com.g9.g9backend.pojo.DTO;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class GetCommunityDTO {
+
+    private List<Community> records;
+
+    private Integer total;
+
+    private Integer pages;
+
+    private Integer current;
+
+    private Integer size;
+
+    @Data
+    @AllArgsConstructor
+    @NoArgsConstructor
+    public static class Community {
+
+        private int communityId;
+
+        private String communityName;
+
+        private String communityPicture;
+
+        private String description;
+
+        private float hot;
+
+        private int threadNumber;
+
+        private int resourceId;
+
+    }
+
+}
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityHotDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityHotDTO.java
new file mode 100644
index 0000000..222eb07
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityHotDTO.java
@@ -0,0 +1,42 @@
+package com.g9.g9backend.pojo.DTO;
+
+import com.g9.g9backend.pojo.Community;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class GetCommunityHotDTO {
+
+    List<Community> communityList;
+
+    @Data
+    @AllArgsConstructor
+    @NoArgsConstructor
+    public static class Community{
+
+        private int communityId;
+
+        private String communityName;
+
+        private String communityPicture;
+
+        private String description;
+
+        private float hot;
+
+        private String type;
+
+        private int threadNumber;
+
+        private int resourceId;
+
+        private int status;
+
+    }
+
+}
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityThreadsDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityThreadsDTO.java
new file mode 100644
index 0000000..d734433
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityThreadsDTO.java
@@ -0,0 +1,44 @@
+package com.g9.g9backend.pojo.DTO;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class GetCommunityThreadsDTO {
+
+    private List<Thread> records;
+
+    private Integer total;
+
+    private Integer pages;
+
+    private Integer current;
+
+    private Integer size;
+
+    @Data
+    @AllArgsConstructor
+    @NoArgsConstructor
+    public static class Thread{
+
+        private int threadId;
+
+        private int userId;
+
+        private String threadPicture;
+
+        private String title;
+
+        private int likes;
+
+        private Date createAt;
+
+    }
+
+}
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/ThreadDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/ThreadDTO.java
new file mode 100644
index 0000000..5e0aec7
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/ThreadDTO.java
@@ -0,0 +1,36 @@
+package com.g9.g9backend.pojo.DTO;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ThreadDTO {
+
+    private int threadId;
+
+    private int userId;
+
+    private String threadPicture;
+
+    private String title;
+
+    private String content;
+
+    private int likes;
+
+    @JsonProperty("isLike")
+    private boolean isLike;
+
+    private Date createAt;
+
+    private int commentNumber;
+
+    private int communityId;
+
+}
diff --git a/src/test/java/com/g9/g9backend/controller/CommunityControllerTest.java b/src/test/java/com/g9/g9backend/controller/CommunityControllerTest.java
new file mode 100644
index 0000000..da9bf57
--- /dev/null
+++ b/src/test/java/com/g9/g9backend/controller/CommunityControllerTest.java
@@ -0,0 +1,268 @@
+package com.g9.g9backend.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.g9.g9backend.pojo.Community;
+import com.g9.g9backend.pojo.Subscription;
+import com.g9.g9backend.pojo.Thread;
+import com.g9.g9backend.service.CommunityService;
+import com.g9.g9backend.service.SubscriptionService;
+import com.g9.g9backend.service.ThreadLikeService;
+import com.g9.g9backend.service.ThreadService;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+@ExtendWith(MockitoExtension.class)
+public class CommunityControllerTest {
+
+    private MockMvc mockMvc;
+
+    @InjectMocks
+    private CommunityController communityController;
+
+    @Mock
+    private CommunityService communityService;
+
+    @Mock
+    private ThreadService threadService;
+
+    @Mock
+    private SubscriptionService subscriptionService;
+
+    @Mock
+    private ThreadLikeService threadLikeService;
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    @BeforeEach
+    public void setup() {
+        mockMvc = MockMvcBuilders.standaloneSetup(communityController).build();
+    }
+
+    @Test
+    public void shouldReturnHotCommunities_whenCallingHotEndpoint() throws Exception {
+        Community c1 = new Community(1, "社区A", "pic", "desc", 9.0f, "type", 5, 1);
+        Page<Community> page = createCommunityPage(List.of(c1));
+        when(communityService.page(any(), any())).thenReturn(page);
+
+        mockMvc.perform(get("/community/hot"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.communityList[0].communityId").value(1));
+    }
+
+    @Test
+    public void shouldReturnCommonCommunities_whenCallingCommonEndpoint() throws Exception {
+        Community c1 = new Community(2, "社区B", "pic", "desc", 7.0f, "type", 3, 1);
+        Page<Community> page = createCommunityPage(List.of(c1));
+        when(communityService.page(any(), any())).thenReturn(page);
+
+        mockMvc.perform(get("/community/common"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.communityList[0].communityId").value(2));
+    }
+
+    @Test
+    public void shouldReturnCommunityInfo_whenIdIsValid() throws Exception {
+        Community community = new Community(3, "社区C", "pic", "desc", 6.0f, "type", 4, 2);
+        when(communityService.getById(3)).thenReturn(community);
+
+        mockMvc.perform(get("/community/info").param("communityId", "3"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.communityId").value(3));
+    }
+
+    @Test
+    public void shouldReturnThreadDetails_whenThreadIdAndUserIdAreValid() throws Exception {
+        Thread thread = mockThread(1, 10);
+        when(threadService.getById(1)).thenReturn(thread);
+        when(threadLikeService.getOne(any())).thenReturn(null);
+
+        mockMvc.perform(get("/thread")
+                        .param("threadId", "1")
+                        .param("userId", "10"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.threadId").value(1))
+                .andExpect(jsonPath("$.userId").value(10));
+    }
+
+    @Test
+    public void shouldPostThread_whenValidRequest() throws Exception {
+        Thread thread = new Thread();
+        thread.setThreadId(100);
+        thread.setCommunityId(1);
+
+        Community community = new Community();
+        community.setCommunityId(1);
+        community.setThreadNumber(5);
+
+        when(communityService.getById(1)).thenReturn(community);
+
+        mockMvc.perform(post("/thread")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(thread)))
+                .andExpect(status().isOk());
+    }
+
+    @Test
+    public void shouldLikeThread_whenValidInput() throws Exception {
+        Thread thread = new Thread();
+        thread.setThreadId(100);
+        thread.setLikes(5);
+
+        when(threadService.getById(100)).thenReturn(thread);
+
+        mockMvc.perform(post("/thread/like")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content("{\"userId\":1, \"threadId\":100}"))
+                .andExpect(status().isOk());
+    }
+
+    @Test
+    public void shouldDeleteThread_whenThreadExists() throws Exception {
+        Thread thread = new Thread();
+        thread.setThreadId(100);
+        thread.setCommunityId(1);
+
+        Community community = new Community();
+        community.setCommunityId(1);
+        community.setThreadNumber(5);
+
+        when(threadService.getById(100)).thenReturn(thread);
+        when(communityService.getById(1)).thenReturn(community);
+
+        mockMvc.perform(delete("/thread").param("threadId", "100"))
+                .andExpect(status().isNoContent());
+    }
+
+    @Test
+    public void shouldCancelLike_whenValidRequest() throws Exception {
+        Thread thread = new Thread();
+        thread.setThreadId(100);
+        thread.setLikes(5);
+
+        when(threadService.getById(100)).thenReturn(thread);
+
+        mockMvc.perform(delete("/thread/like")
+                        .param("userId", "1")
+                        .param("threadId", "100"))
+                .andExpect(status().isNoContent());
+    }
+
+    @Test
+    public void shouldReturnCommunities_whenSearchingByType() throws Exception {
+        Community c = new Community(1, "搜索社区", "pic", "desc", 9.5f, "test", 8, 5);
+        Page<Community> page = createCommunityPage(List.of(c));
+
+        when(communityService.page(any(), any())).thenReturn(page);
+
+        mockMvc.perform(get("/community")
+                        .param("searchValue", "搜索")
+                        .param("type", "test")
+                        .param("pageNumber", "1")
+                        .param("rows", "10"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.records[0].communityId").value(1));
+    }
+
+    @Test
+    public void shouldReturnHotThreads_whenOptionIsHot() throws Exception {
+        Thread t = new Thread();
+        t.setThreadId(1);
+        t.setUserId(2);
+        t.setTitle("热帖");
+        t.setLikes(10);
+        Page<Thread> page = new Page<>(1, 10);
+        page.setRecords(List.of(t));
+        page.setTotal(1L);
+
+        when(threadService.page(any(), any())).thenReturn(page);
+
+        mockMvc.perform(get("/community/threads")
+                        .param("communityId", "1")
+                        .param("pageNumber", "1")
+                        .param("rows", "10")
+                        .param("option", "最高热度")
+                        .param("searchValue", "")
+                        .param("userId", "2"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.records[0].threadId").value(1));
+    }
+
+    @Test
+    public void shouldReturnFollowedThreads_whenOptionIsFollowed() throws Exception {
+        Subscription s = new Subscription();
+        s.setUserId(1);
+        s.setFollowerId(2);
+
+        List<Subscription> subscriptions = new ArrayList<>();
+        subscriptions.add(s);
+
+        @SuppressWarnings("unchecked")
+        LambdaQueryWrapper<Subscription> wrapper = any(LambdaQueryWrapper.class);
+        when(subscriptionService.list(wrapper)).thenReturn(subscriptions);
+
+        Thread t = new Thread();
+        t.setThreadId(2);
+        t.setUserId(2);
+        t.setTitle("关注者帖子");
+        t.setLikes(8);
+
+        Page<Thread> page = new Page<>(1, 10);
+        page.setRecords(List.of(t));
+        page.setTotal(1L);
+
+        when(threadService.page(any(), any())).thenReturn(page);
+
+        mockMvc.perform(get("/community/threads")
+                        .param("communityId", "1")
+                        .param("pageNumber", "1")
+                        .param("rows", "10")
+                        .param("option", "关注者")
+                        .param("searchValue", "")
+                        .param("userId", "1"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.records[0].threadId").value(2));
+    }
+
+    @NotNull
+    private Page<Community> createCommunityPage(List<Community> communities) {
+        Page<Community> page = new Page<>(1, 3);
+        page.setRecords(communities);
+        page.setTotal(communities.size());
+        return page;
+    }
+
+    @NotNull
+    @SuppressWarnings("SameParameterValue")
+    private Thread mockThread(int threadId, int userId) {
+        Thread thread = new Thread();
+        thread.setThreadId(threadId);
+        thread.setUserId(userId);
+        thread.setThreadPicture("pic");
+        thread.setTitle("title");
+        thread.setContent("content");
+        thread.setLikes(5);
+        thread.setCreateAt(new Date());
+        thread.setCommentNumber(2);
+        thread.setCommunityId(99);
+        return thread;
+    }
+}