package com.tourguide.integration.wechat; import com.fasterxml.jackson.databind.ObjectMapper; import com.tourguide.common.exception.BusinessException; import com.tourguide.config.properties.WechatMiniProgramProperties; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.http.MediaType; import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.web.client.RestTemplate; import java.time.Duration; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.test.web.client.match.MockRestRequestMatchers.content; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; class WechatMiniProgramClientTest { private StringRedisTemplate redisTemplate; private ValueOperations valueOperations; private MockRestServiceServer server; private WechatMiniProgramClient client; @BeforeEach @SuppressWarnings("unchecked") void setUp() { RestTemplate restTemplate = new RestTemplate(); server = MockRestServiceServer.bindTo(restTemplate).build(); redisTemplate = mock(StringRedisTemplate.class); valueOperations = mock(ValueOperations.class); when(redisTemplate.opsForValue()).thenReturn(valueOperations); when(valueOperations.get(anyString())).thenReturn(null); WechatMiniProgramProperties properties = new WechatMiniProgramProperties(); properties.setAppId("test-app"); properties.setAppSecret("test-secret"); client = new WechatMiniProgramClient(properties, restTemplate, redisTemplate, new ObjectMapper()); } @Test void shouldExchangeLoginAndPhoneCodes() { server.expect(requestTo("https://api.weixin.qq.com/sns/jscode2session?appid=test-app&secret=test-secret&js_code=login-code&grant_type=authorization_code")) .andRespond(withSuccess("{\"openid\":\"openid-1\",\"session_key\":\"session-key\"}", MediaType.TEXT_PLAIN)); server.expect(requestTo("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=test-app&secret=test-secret")) .andRespond(withSuccess("{\"access_token\":\"access-token\",\"expires_in\":7200}", MediaType.TEXT_PLAIN)); server.expect(requestTo("https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=access-token")) .andExpect(content().json("{\"code\":\"phone-code\"}")) .andRespond(withSuccess("{\"errcode\":0,\"errmsg\":\"ok\",\"phone_info\":{\"purePhoneNumber\":\"13800138000\"}}", MediaType.TEXT_PLAIN)); WechatPhoneAuthorization authorization = client.authorizePhoneLogin("login-code", "phone-code"); assertThat(authorization.getOpenid()).isEqualTo("openid-1"); assertThat(authorization.getMobile()).isEqualTo("13800138000"); verify(valueOperations).set("tourguide:wechat:mini:access-token:test-app", "access-token", Duration.ofSeconds(6900)); server.verify(); } @Test void shouldReportWechatConfigurationError() { server.expect(requestTo("https://api.weixin.qq.com/sns/jscode2session?appid=test-app&secret=test-secret&js_code=login-code&grant_type=authorization_code")) .andRespond(withSuccess("{\"errcode\":40125,\"errmsg\":\"invalid appsecret\"}", MediaType.TEXT_PLAIN)); assertThatThrownBy(() -> client.authorizePhoneLogin("login-code", "phone-code")) .isInstanceOf(BusinessException.class) .hasMessage("微信小程序 AppID 或 AppSecret 配置错误(微信错误码:40125)"); server.verify(); } }