package kr.itn.itnhub.timeline; import kr.itn.itnhub.AbstractDbTest; import kr.itn.itnhub.feed.FileView; import kr.itn.itnhub.mattermost.MattermostClient; import kr.itn.itnhub.mattermost.MattermostException; import kr.itn.itnhub.org.Organization; import kr.itn.itnhub.org.OrganizationMapper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; import java.util.List; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @AutoConfigureMockMvc @WithMockUser(roles = "ADMIN") class TimelineControllerTest extends AbstractDbTest { @Autowired MockMvc mvc; @Autowired OrganizationMapper mapper; @Autowired JdbcTemplate jdbc; @MockBean MattermostClient mattermost; private Long orgId; @BeforeEach void setUp() { jdbc.update("delete from organization"); Organization org = new Organization(); org.setOrgNo("001"); org.setOrgName("국제방송교류재단"); org.setChannelSlug("001"); mapper.upsertBySeed(org); orgId = mapper.findAll().get(0).getId(); mapper.updateChannelIdMj(orgId, "chan-mj"); mapper.updateChannelIdLaw(orgId, "chan-law"); } private void insertStageHistory(long orgId, int stage, long epochSeconds) { jdbc.update("insert into stage_history (org_id, stage, changed_at) values (?, ?, to_timestamp(?))", orgId, stage, epochSeconds); } private void insertMemo(long orgId, String contactName, String body, long epochSeconds) { jdbc.update("insert into work_memo (org_id, contact_name, body, created_at) " + "values (?, ?, ?, to_timestamp(?))", orgId, contactName, body, epochSeconds); } @Test void 세_종류_이벤트를_최신순으로_합쳐서_돌려준다() throws Exception { insertStageHistory(orgId, 1, 1_000L); insertMemo(orgId, "송민지", "안녕하세요\n둘째줄입니다", 2_000L); when(mattermost.collectChannelFiles("chan-mj")).thenReturn(List.of( new FileView("f1", "보고서.pdf", 100L, "application/pdf", 3_000_000L, "박아이티"))); when(mattermost.collectChannelFiles("chan-law")).thenReturn(List.of()); mvc.perform(get("/api/orgs/{id}/timeline", orgId)) .andExpect(status().isOk()) .andExpect(jsonPath("$", org.hamcrest.Matchers.hasSize(3))) .andExpect(jsonPath("$[0].type").value("FILE")) .andExpect(jsonPath("$[0].title").value("보고서.pdf")) .andExpect(jsonPath("$[0].detail").value("문정원 · 박아이티")) .andExpect(jsonPath("$[1].type").value("MEMO")) .andExpect(jsonPath("$[1].title").value("송민지")) .andExpect(jsonPath("$[1].detail").value("안녕하세요")) .andExpect(jsonPath("$[2].type").value("STAGE")) .andExpect(jsonPath("$[2].title").value("1")) .andExpect(jsonPath("$[2].detail").doesNotExist()); } @Test void Mattermost_조회가_실패해도_단계와_메모_이벤트는_그대로_200으로_돌려준다() throws Exception { insertStageHistory(orgId, 2, 1_000L); insertMemo(orgId, "김문정", "메모내용", 2_000L); when(mattermost.collectChannelFiles("chan-mj")).thenThrow(new MattermostException("서버 오류")); when(mattermost.collectChannelFiles("chan-law")).thenThrow(new MattermostException("서버 오류")); mvc.perform(get("/api/orgs/{id}/timeline", orgId)) .andExpect(status().isOk()) .andExpect(jsonPath("$", org.hamcrest.Matchers.hasSize(2))) .andExpect(jsonPath("$[0].type").value("MEMO")) .andExpect(jsonPath("$[1].type").value("STAGE")); } @Test void 존재하지_않는_기관이면_404다() throws Exception { long missingId = orgId + 999999L; mvc.perform(get("/api/orgs/{id}/timeline", missingId)) .andExpect(status().isNotFound()); } @Test void 기록이_없으면_빈_배열을_돌려준다() throws Exception { when(mattermost.collectChannelFiles("chan-mj")).thenReturn(List.of()); when(mattermost.collectChannelFiles("chan-law")).thenReturn(List.of()); mvc.perform(get("/api/orgs/{id}/timeline", orgId)) .andExpect(status().isOk()) .andExpect(jsonPath("$", org.hamcrest.Matchers.hasSize(0))); } }