File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
package kr.itn.itnhub.org;
import kr.itn.itnhub.AbstractDbTest;
import kr.itn.itnhub.contact.Contact;
import kr.itn.itnhub.contact.ContactMapper;
import kr.itn.itnhub.mattermost.MattermostClient;
import kr.itn.itnhub.mattermost.MattermostException;
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.http.MediaType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.nullValue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
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.request.MockMvcRequestBuilders.put;
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 OrganizationControllerTest extends AbstractDbTest {
@Autowired
MockMvc mvc;
@Autowired
OrganizationMapper mapper;
@Autowired
ContactMapper contactMapper;
@Autowired
JdbcTemplate jdbc;
@MockBean
MattermostClient mattermost;
private Long orgId;
@BeforeEach
void setUp() {
jdbc.update("delete from organization");
jdbc.update("delete from contact");
Organization org = new Organization();
org.setOrgNo("001");
org.setOrgName("국제방송교류재단");
org.setChannelSlug("001");
mapper.upsertBySeed(org);
orgId = mapper.findAll().get(0).getId();
}
private Long createContact(String category, String name) {
Contact contact = new Contact();
contact.setCategory(category);
contact.setName(name);
contactMapper.insert(contact);
return contact.getId();
}
@Test
void 기관_목록을_상태와_함께_돌려준다() throws Exception {
mvc.perform(get("/api/orgs"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].orgNo").value("001"))
.andExpect(jsonPath("$[0].orgName").value("국제방송교류재단"))
.andExpect(jsonPath("$[0].status").value("INFO_PENDING"));
}
@Test
void 신청기관_담당자를_배정하면_생성가능_상태가_된다() throws Exception {
Long applicantId = createContact("APPLICANT", "송민지");
mvc.perform(put("/api/orgs/{id}/assignments", orgId)
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content("{\"applicantContactId\": " + applicantId + "}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("READY"))
.andExpect(jsonPath("$.applicant.name").value("송민지"));
assertThat(mapper.findById(orgId).getApplicantContactId()).isEqualTo(applicantId);
}
@Test
void 문정원_담당자와_변호사를_함께_배정하면_응답과_재조회에_모두_반영된다() throws Exception {
Long applicantId = createContact("APPLICANT", "송민지");
Long mjId = createContact("MJ", "김문정");
Long itnId = createContact("ITN", "박아이티");
Long lawyerId = createContact("LAWYER", "이변호");
mvc.perform(put("/api/orgs/{id}/assignments", orgId)
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"applicantContactId": %d,
"mjContactId": %d,
"itnContactId": %d,
"lawyerContactId": %d,
"lawyerAssignedDate": "2026-07-21"
}
""".formatted(applicantId, mjId, itnId, lawyerId)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.mj.name").value("김문정"))
.andExpect(jsonPath("$.itn.name").value("박아이티"))
.andExpect(jsonPath("$.lawyer.name").value("이변호"))
.andExpect(jsonPath("$.lawyerAssignedDate").value("2026-07-21"));
mvc.perform(get("/api/orgs"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].mj.name").value("김문정"))
.andExpect(jsonPath("$[0].itn.name").value("박아이티"))
.andExpect(jsonPath("$[0].lawyer.name").value("이변호"));
}
@Test
void 신청기관_배정없이_문정원_변호사만_배정해도_저장에_성공하고_정보대기로_남는다() throws Exception {
Long mjId = createContact("MJ", "김문정");
mvc.perform(put("/api/orgs/{id}/assignments", orgId)
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content("{\"mjContactId\": " + mjId + "}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("INFO_PENDING"))
.andExpect(jsonPath("$.applicant").value(nullValue()))
.andExpect(jsonPath("$.mj.name").value("김문정"));
}
@Test
void 배정_해제는_null을_보내면_반영되어_정보대기로_돌아간다() throws Exception {
Long applicantId = createContact("APPLICANT", "송민지");
mapper.updateAssignments(orgId, applicantId, null, null, null, null);
mvc.perform(put("/api/orgs/{id}/assignments", orgId)
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content("{\"applicantContactId\": null}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("INFO_PENDING"))
.andExpect(jsonPath("$.applicant").value(nullValue()));
}
@Test
void 아이티앤_담당자를_배정하면_응답과_재조회에_반영된다() throws Exception {
Long itnId = createContact("ITN", "박아이티");
mvc.perform(put("/api/orgs/{id}/assignments", orgId)
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content("{\"itnContactId\": " + itnId + "}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.itn.name").value("박아이티"));
mvc.perform(get("/api/orgs"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].itn.name").value("박아이티"));
}
@Test
void 구분이_맞지_않는_담당자를_아이티앤에_배정하면_400이다() throws Exception {
Long lawyerId = createContact("LAWYER", "이변호");
mvc.perform(put("/api/orgs/{id}/assignments", orgId)
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content("{\"itnContactId\": " + lawyerId + "}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.message").value(containsString("아이티앤 담당자")));
}
@Test
void 아이티앤_담당자_배정_해제는_null을_보내면_반영된다() throws Exception {
Long itnId = createContact("ITN", "박아이티");
mapper.updateAssignments(orgId, null, null, itnId, null, null);
mvc.perform(put("/api/orgs/{id}/assignments", orgId)
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content("{\"itnContactId\": null}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.itn").value(nullValue()));
assertThat(mapper.findById(orgId).getItnContactId()).isNull();
}
@Test
void 존재하지_않는_기관ID로_배정을_수정하면_404다() throws Exception {
long missingId = orgId + 999999L;
mvc.perform(put("/api/orgs/{id}/assignments", missingId)
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content("{}"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.message").value(containsString(String.valueOf(missingId))));
}
@Test
void 존재하지_않는_담당자ID를_배정하면_400이다() throws Exception {
long missingContactId = 999999L;
mvc.perform(put("/api/orgs/{id}/assignments", orgId)
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content("{\"applicantContactId\": " + missingContactId + "}"))
.andExpect(status().isBadRequest());
}
@Test
void 구분이_맞지_않는_담당자를_배정하면_400이다() throws Exception {
Long mjContactId = createContact("MJ", "김문정");
mvc.perform(put("/api/orgs/{id}/assignments", orgId)
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content("{\"applicantContactId\": " + mjContactId + "}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.message").value(containsString("신청기관 담당자")));
}
@Test
void 채널생성을_요청하면_결과를_돌려준다() throws Exception {
Long applicantId = createContact("APPLICANT", "송민지");
mapper.updateAssignments(orgId, applicantId, null, null, null, null);
when(mattermost.findChannelIdByInternalName(anyString())).thenReturn(Optional.empty());
when(mattermost.findChannelIdByDisplayName(anyString())).thenReturn(Optional.empty());
when(mattermost.createPrivateChannel(anyString(), anyString())).thenReturn("chan");
mvc.perform(post("/api/orgs/{id}/channels", orgId).with(csrf()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.mj").value("CREATED"))
.andExpect(jsonPath("$.law").value("CREATED"));
}
@Test
void 담당자배정없이_채널생성하면_실패결과를_돌려준다() throws Exception {
mvc.perform(post("/api/orgs/{id}/channels", orgId).with(csrf()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.mj").value("FAILED"))
.andExpect(jsonPath("$.law").value("FAILED"))
.andExpect(jsonPath("$.message").value(containsString("신청기관 담당자")));
}
/**
* Finding 2 회귀 테스트: 문정원 채널은 성공하고 법률검토 채널만 실패하는 실제 혼합
* 결과를 컨트롤러 경계에서 검증한다. 기존 테스트는 전부 성공 아니면(둘 다 성공)
* 담당자 정보 누락으로 둘 다 강제 FAILED인 경로만 다뤄, 진짜 부분 실패가 HTTP
* 200으로 내려가는지는 한 번도 실행되지 않았다.
*/
@Test
void 법률검토만_실패해도_200과_부분실패_결과를_돌려준다() throws Exception {
Long applicantId = createContact("APPLICANT", "송민지");
mapper.updateAssignments(orgId, applicantId, null, null, null, null);
when(mattermost.findChannelIdByInternalName(anyString())).thenReturn(Optional.empty());
when(mattermost.findChannelIdByDisplayName(anyString())).thenReturn(Optional.empty());
when(mattermost.createPrivateChannel(eq("org-001-mj"), anyString())).thenReturn("id-mj");
when(mattermost.createPrivateChannel(eq("org-001-law"), anyString()))
.thenThrow(new MattermostException("법률검토 서버 오류"));
mvc.perform(post("/api/orgs/{id}/channels", orgId).with(csrf()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.mj").value("CREATED"))
.andExpect(jsonPath("$.law").value("FAILED"))
.andExpect(jsonPath("$.message").value(containsString("법률검토")));
}
}