package kr.itn.itnhub.mattermost; import com.github.tomakehurst.wiremock.WireMockServer; import kr.itn.itnhub.config.MattermostProperties; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.client.JdkClientHttpRequestFactory; import org.springframework.web.client.RestClient; import java.net.http.HttpClient; import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; class MattermostRestClientTest { private WireMockServer server; private MattermostRestClient client; @BeforeEach void setUp() { server = new WireMockServer(options().dynamicPort()); server.start(); MattermostProperties props = new MattermostProperties( "http://localhost:" + server.port(), "test-token", "team123", "문정원", "법률검토"); // 이 HTTP/1.1 고정은 테스트 하네스(WireMock/Jetty)만의 제약이다: JDK 21 HttpClient의 // 기본 h2c 업그레이드 협상이 WireMock 상대로 POST 요청에서 간헐적으로 RST_STREAM을 // 유발하기 때문에 여기서만 고정한다. 운영 환경은 HTTPS/ALPN으로 협상하므로 해당되지 않는다. HttpClient httpClient = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_1_1) .build(); RestClient.Builder builder = RestClient.builder() .requestFactory(new JdkClientHttpRequestFactory(httpClient)); client = new MattermostRestClient(props, builder); } @AfterEach void tearDown() { server.stop(); } @Test void 내부명으로_채널을_찾으면_id를_돌려준다() { server.stubFor(get(urlEqualTo("/api/v4/teams/team123/channels/name/org-001-mj")) .willReturn(okJson("{\"id\":\"chan001mj\",\"name\":\"org-001-mj\"}"))); assertThat(client.findChannelIdByInternalName("org-001-mj")) .contains("chan001mj"); } @Test void 내부명으로_채널이_없으면_빈값이다() { server.stubFor(get(urlEqualTo("/api/v4/teams/team123/channels/name/org-999-mj")) .willReturn(aResponse().withStatus(404))); assertThat(client.findChannelIdByInternalName("org-999-mj")).isEmpty(); } @Test void 토큰을_Authorization_헤더로_보낸다() { server.stubFor(get(urlEqualTo("/api/v4/teams/team123/channels/name/org-001-mj")) .willReturn(okJson("{\"id\":\"chan001mj\"}"))); client.findChannelIdByInternalName("org-001-mj"); server.verify(getRequestedFor(urlEqualTo("/api/v4/teams/team123/channels/name/org-001-mj")) .withHeader("Authorization", equalTo("Bearer test-token"))); } @Test void 표시명으로_내_채널목록에서_id를_찾는다() { server.stubFor(get(urlPathEqualTo("/api/v4/users/me/teams/team123/channels")) .willReturn(okJson(""" [ {"id":"aaa","display_name":"001_국제방송교류재단 (문정원)"}, {"id":"bbb","display_name":"001_국제방송교류재단 (법률검토)"} ] """))); assertThat(client.findChannelIdByDisplayName("001_국제방송교류재단 (법률검토)")) .contains("bbb"); assertThat(client.findChannelIdByDisplayName("002_없는기관 (문정원)")) .isEmpty(); } @Test void 비공개_채널을_생성하고_id를_돌려준다() { server.stubFor(post(urlEqualTo("/api/v4/channels")) .willReturn(okJson("{\"id\":\"newchan\"}"))); String id = client.createPrivateChannel("org-002-mj", "002_세종학당재단 (문정원)"); assertThat(id).isEqualTo("newchan"); server.verify(postRequestedFor(urlEqualTo("/api/v4/channels")) .withRequestBody(matchingJsonPath("$.team_id", equalTo("team123"))) .withRequestBody(matchingJsonPath("$.name", equalTo("org-002-mj"))) .withRequestBody(matchingJsonPath("$.display_name", equalTo("002_세종학당재단 (문정원)"))) .withRequestBody(matchingJsonPath("$.type", equalTo("P")))); } @Test void 서버가_오류를_주면_MattermostException을_던진다() { server.stubFor(post(urlEqualTo("/api/v4/channels")) .willReturn(aResponse().withStatus(500).withBody("boom"))); assertThatThrownBy(() -> client.createPrivateChannel("org-003-mj", "003_기관 (문정원)")) .isInstanceOf(MattermostException.class); } }