package kr.itn.itnhub.memo; import kr.itn.itnhub.AbstractDbTest; 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.http.MediaType; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; import static org.hamcrest.Matchers.containsString; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 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 WorkMemoControllerTest extends AbstractDbTest { @Autowired MockMvc mvc; @Autowired OrganizationMapper orgMapper; @Autowired WorkMemoMapper memoMapper; @Autowired JdbcTemplate jdbc; private Long orgId; @BeforeEach void setUp() { jdbc.update("delete from work_memo"); jdbc.update("delete from organization"); Organization org = new Organization(); org.setOrgNo("001"); org.setOrgName("국제방송교류재단"); org.setChannelSlug("001"); orgMapper.upsertBySeed(org); orgId = orgMapper.findAll().get(0).getId(); } @Test void 메모를_등록하면_생성된_메모를_돌려주고_목록에도_보인다() throws Exception { mvc.perform(post("/api/orgs/{id}/memos", orgId) .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(""" { "contactName": "송민지 과장", "body": "채널 생성 일정 문의" } """)) .andExpect(status().isOk()) .andExpect(jsonPath("$.contactName").value("송민지 과장")) .andExpect(jsonPath("$.body").value("채널 생성 일정 문의")) .andExpect(jsonPath("$.createdAt").isNumber()); mvc.perform(get("/api/orgs/{id}/memos", orgId)) .andExpect(status().isOk()) .andExpect(jsonPath("$[0].contactName").value("송민지 과장")) .andExpect(jsonPath("$[0].body").value("채널 생성 일정 문의")); } @Test void 목록은_최신순이다() throws Exception { mvc.perform(post("/api/orgs/{id}/memos", orgId) .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(""" {"contactName": "송민지", "body": "첫번째 통화"} """)) .andExpect(status().isOk()); mvc.perform(post("/api/orgs/{id}/memos", orgId) .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(""" {"contactName": "송민지", "body": "두번째 통화"} """)) .andExpect(status().isOk()); mvc.perform(get("/api/orgs/{id}/memos", orgId)) .andExpect(status().isOk()) .andExpect(jsonPath("$[0].body").value("두번째 통화")) .andExpect(jsonPath("$[1].body").value("첫번째 통화")); } @Test void 메모_내용이_비면_400과_한글_메시지를_돌려준다() throws Exception { mvc.perform(post("/api/orgs/{id}/memos", orgId) .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(""" {"contactName": "송민지", "body": ""} """)) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.message").value(containsString("메모 내용"))); } @Test void 존재하지_않는_기관이면_404다() throws Exception { long missingId = orgId + 999999L; mvc.perform(get("/api/orgs/{id}/memos", missingId)) .andExpect(status().isNotFound()) .andExpect(jsonPath("$.message").value(containsString(String.valueOf(missingId)))); } @Test void 메모를_삭제하면_204와_함께_목록에서_사라진다() throws Exception { mvc.perform(post("/api/orgs/{id}/memos", orgId) .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(""" {"contactName": "송민지", "body": "삭제될 메모"} """)) .andExpect(status().isOk()); Long memoId = memoMapper.findByOrg(orgId).get(0).getId(); mvc.perform(delete("/api/orgs/{id}/memos/{memoId}", orgId, memoId).with(csrf())) .andExpect(status().isNoContent()); mvc.perform(get("/api/orgs/{id}/memos", orgId)) .andExpect(status().isOk()) .andExpect(jsonPath("$").isEmpty()); } @Test void 다른_기관_경로로_삭제하면_404다() throws Exception { mvc.perform(post("/api/orgs/{id}/memos", orgId) .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(""" {"contactName": "송민지", "body": "삭제될 메모"} """)) .andExpect(status().isOk()); Long memoId = memoMapper.findByOrg(orgId).get(0).getId(); Organization other = new Organization(); other.setOrgNo("002"); other.setOrgName("다른기관"); other.setChannelSlug("002"); orgMapper.upsertBySeed(other); Long otherOrgId = orgMapper.findAll().stream() .filter(o -> o.getOrgNo().equals("002")) .findFirst().get().getId(); mvc.perform(delete("/api/orgs/{id}/memos/{memoId}", otherOrgId, memoId).with(csrf())) .andExpect(status().isNotFound()); } @Test @WithAnonymousUser void 인증없이_호출하면_401이다() throws Exception { mvc.perform(get("/api/orgs/{id}/memos", orgId)) .andExpect(status().isUnauthorized()); } }