test(send): integration tests for scene-based routing
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
package com.sino.mci.send.service;
|
||||
|
||||
import com.sino.mci.channel.common.model.AccountType;
|
||||
import com.sino.mci.channel.common.model.ChannelType;
|
||||
import com.sino.mci.channel.entity.ChannelAccount;
|
||||
import com.sino.mci.channel.entity.ChannelAccountConversation;
|
||||
import com.sino.mci.channel.entity.ChannelSubject;
|
||||
import com.sino.mci.channel.entity.Conversation;
|
||||
import com.sino.mci.channel.repository.ChannelAccountConversationMapper;
|
||||
import com.sino.mci.channel.repository.ChannelAccountMapper;
|
||||
import com.sino.mci.channel.repository.ChannelSubjectMapper;
|
||||
import com.sino.mci.channel.repository.ConversationMapper;
|
||||
import com.sino.mci.crm.entity.ChannelIdentity;
|
||||
import com.sino.mci.crm.entity.Person;
|
||||
import com.sino.mci.crm.repository.ChannelIdentityMapper;
|
||||
import com.sino.mci.crm.repository.PersonMapper;
|
||||
import com.sino.mci.send.entity.SendRecord;
|
||||
import com.sino.mci.send.repository.SendRecordMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.sino.mci.test.AuthTestHelper.createTenantAndLogin;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 基于会话场景与目标类型的发送路由集成测试。
|
||||
*
|
||||
* <p>覆盖自动路由策略:
|
||||
* <ul>
|
||||
* <li>内部群(conversation_scene = 1)→ WECOM / WECOM_BOT</li>
|
||||
* <li>外部群(conversation_scene = 2)→ WECHAT_PERSONAL</li>
|
||||
* <li>内部人员 → WECOM_AGENT</li>
|
||||
* <li>外部人员 → WECOM_AGENT</li>
|
||||
* </ul>
|
||||
*/
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@Transactional
|
||||
@TestPropertySource(properties = {
|
||||
"spring.rabbitmq.listener.simple.auto-startup=false"
|
||||
})
|
||||
class SendRoutingIntegrationTest {
|
||||
|
||||
private static final String CHANNEL_TYPE_WECOM = ChannelType.WECOM.name().toLowerCase();
|
||||
private static final String CHANNEL_TYPE_WECHAT_PERSONAL = ChannelType.WECHAT_PERSONAL.name().toLowerCase();
|
||||
|
||||
private static final int CONVERSATION_SCENE_INTERNAL = 1;
|
||||
private static final int CONVERSATION_SCENE_EXTERNAL = 2;
|
||||
|
||||
private static final int PERSON_TYPE_INTERNAL = 1;
|
||||
private static final int PERSON_TYPE_EXTERNAL = 3;
|
||||
|
||||
private static final int STATUS_ENABLED = 1;
|
||||
private static final int BINDING_TYPE_SEND_PRIMARY = 1;
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private ChannelSubjectMapper subjectMapper;
|
||||
|
||||
@Autowired
|
||||
private ChannelAccountMapper accountMapper;
|
||||
|
||||
@Autowired
|
||||
private ConversationMapper conversationMapper;
|
||||
|
||||
@Autowired
|
||||
private ChannelAccountConversationMapper accountConversationMapper;
|
||||
|
||||
@Autowired
|
||||
private PersonMapper personMapper;
|
||||
|
||||
@Autowired
|
||||
private ChannelIdentityMapper channelIdentityMapper;
|
||||
|
||||
@Autowired
|
||||
private SendRecordMapper sendRecordMapper;
|
||||
|
||||
@Test
|
||||
void shouldRouteInternalGroupToWeComBot() throws Exception {
|
||||
String tenantCode = "routing-int-group-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
String authToken = createTenantAndLogin(mockMvc, tenantCode, "内部群路由测试");
|
||||
|
||||
ChannelSubject subject = createWeComSubject(tenantCode);
|
||||
ChannelAccount botAccount = createWeComBotAccount(tenantCode, subject.getId());
|
||||
Conversation conversation = createConversation(tenantCode, subject.getId(),
|
||||
CONVERSATION_SCENE_INTERNAL, "内部群");
|
||||
bindAccountToConversation(botAccount.getId(), conversation.getId());
|
||||
|
||||
String taskId = sendTestMessage(authToken, tenantCode,
|
||||
buildConversationPayload(conversation.getId()));
|
||||
|
||||
List<SendRecord> records = sendRecordMapper.selectList(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SendRecord>()
|
||||
.eq(SendRecord::getTenantCode, tenantCode)
|
||||
.eq(SendRecord::getTaskId, taskId));
|
||||
|
||||
assertThat(records).hasSize(1);
|
||||
SendRecord record = records.get(0);
|
||||
assertThat(record.getChannelType()).isEqualTo(CHANNEL_TYPE_WECOM);
|
||||
assertThat(record.getAccountId()).isEqualTo(botAccount.getId());
|
||||
assertThat(record.getConversationId()).isEqualTo(conversation.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRouteExternalGroupToWeChatPersonal() throws Exception {
|
||||
String tenantCode = "routing-ext-group-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
String authToken = createTenantAndLogin(mockMvc, tenantCode, "外部群路由测试");
|
||||
|
||||
ChannelSubject subject = createWeComSubject(tenantCode);
|
||||
ChannelAccount pywechatAccount = createWeChatPersonalAccount(tenantCode, subject.getId());
|
||||
Conversation conversation = createConversation(tenantCode, subject.getId(),
|
||||
CONVERSATION_SCENE_EXTERNAL, "外部群");
|
||||
bindAccountToConversation(pywechatAccount.getId(), conversation.getId());
|
||||
|
||||
String taskId = sendTestMessage(authToken, tenantCode,
|
||||
buildConversationPayload(conversation.getId()));
|
||||
|
||||
List<SendRecord> records = sendRecordMapper.selectList(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SendRecord>()
|
||||
.eq(SendRecord::getTenantCode, tenantCode)
|
||||
.eq(SendRecord::getTaskId, taskId));
|
||||
|
||||
assertThat(records).hasSize(1);
|
||||
SendRecord record = records.get(0);
|
||||
assertThat(record.getChannelType()).isEqualTo(CHANNEL_TYPE_WECHAT_PERSONAL);
|
||||
assertThat(record.getAccountId()).isEqualTo(pywechatAccount.getId());
|
||||
assertThat(record.getConversationId()).isEqualTo(conversation.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRouteInternalPersonToWeComAgent() throws Exception {
|
||||
String tenantCode = "routing-int-person-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
String authToken = createTenantAndLogin(mockMvc, tenantCode, "内部人员路由测试");
|
||||
|
||||
ChannelSubject subject = createWeComSubject(tenantCode);
|
||||
ChannelAccount agentAccount = createWeComAgentAccount(tenantCode, subject.getId());
|
||||
Person person = createPerson(tenantCode, "int-user", "内部人员", PERSON_TYPE_INTERNAL);
|
||||
createChannelIdentity(tenantCode, person.getId(), subject.getId(), "int-user");
|
||||
|
||||
String taskId = sendTestMessage(authToken, tenantCode,
|
||||
buildPersonPayload(person.getId()));
|
||||
|
||||
List<SendRecord> records = sendRecordMapper.selectList(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SendRecord>()
|
||||
.eq(SendRecord::getTenantCode, tenantCode)
|
||||
.eq(SendRecord::getTaskId, taskId));
|
||||
|
||||
assertThat(records).hasSize(1);
|
||||
SendRecord record = records.get(0);
|
||||
assertThat(record.getChannelType()).isEqualTo(CHANNEL_TYPE_WECOM);
|
||||
assertThat(record.getAccountId()).isEqualTo(agentAccount.getId());
|
||||
assertThat(record.getPersonId()).isEqualTo(person.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRouteExternalPersonToWeComAgent() throws Exception {
|
||||
String tenantCode = "routing-ext-person-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
String authToken = createTenantAndLogin(mockMvc, tenantCode, "外部人员路由测试");
|
||||
|
||||
ChannelSubject subject = createWeComSubject(tenantCode);
|
||||
ChannelAccount agentAccount = createWeComAgentAccount(tenantCode, subject.getId());
|
||||
Person person = createPerson(tenantCode, "ext-user", "外部人员", PERSON_TYPE_EXTERNAL);
|
||||
createChannelIdentity(tenantCode, person.getId(), subject.getId(), "ext-user");
|
||||
|
||||
String taskId = sendTestMessage(authToken, tenantCode,
|
||||
buildPersonPayload(person.getId()));
|
||||
|
||||
List<SendRecord> records = sendRecordMapper.selectList(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SendRecord>()
|
||||
.eq(SendRecord::getTenantCode, tenantCode)
|
||||
.eq(SendRecord::getTaskId, taskId));
|
||||
|
||||
assertThat(records).hasSize(1);
|
||||
SendRecord record = records.get(0);
|
||||
assertThat(record.getChannelType()).isEqualTo(CHANNEL_TYPE_WECOM);
|
||||
assertThat(record.getAccountId()).isEqualTo(agentAccount.getId());
|
||||
assertThat(record.getPersonId()).isEqualTo(person.getId());
|
||||
}
|
||||
|
||||
private String sendTestMessage(String authToken, String tenantCode, String payload) throws Exception {
|
||||
String response = mockMvc.perform(post("/msg-platform/api/v1/send/test")
|
||||
.contextPath("/msg-platform")
|
||||
.header("Authorization", authToken)
|
||||
.header("X-Tenant-Code", tenantCode)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.totalCount").value(1))
|
||||
.andExpect(jsonPath("$.data.status").value("simulated"))
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
return com.jayway.jsonpath.JsonPath.read(response, "$.data.taskId");
|
||||
}
|
||||
|
||||
private String buildConversationPayload(Long conversationId) {
|
||||
return String.format(
|
||||
"{\"targetType\":\"conversation\",\"targetValue\":{\"conversation_id\":%d},\"contentType\":\"text\",\"content\":{\"text\":\"hello\"},\"channelStrategy\":{\"channel_type\":\"wecom\",\"strategy\":\"primary\"}}",
|
||||
conversationId);
|
||||
}
|
||||
|
||||
private String buildPersonPayload(Long personId) {
|
||||
return String.format(
|
||||
"{\"targetType\":\"person\",\"targetValue\":{\"person_id\":%d},\"contentType\":\"text\",\"content\":{\"text\":\"hello\"},\"channelStrategy\":{\"channel_type\":\"wecom\"}}",
|
||||
personId);
|
||||
}
|
||||
|
||||
private ChannelSubject createWeComSubject(String tenantCode) {
|
||||
ChannelSubject subject = new ChannelSubject();
|
||||
subject.setTenantCode(tenantCode);
|
||||
subject.setChannelType(CHANNEL_TYPE_WECOM);
|
||||
subject.setSubjectCode("sub-" + UUID.randomUUID().toString().substring(0, 8));
|
||||
subject.setSubjectName("测试主体");
|
||||
subject.setCorpId("corp-" + UUID.randomUUID().toString().substring(0, 8));
|
||||
subject.setAgentId("1000002");
|
||||
subject.setAgentSecret("secret-" + UUID.randomUUID());
|
||||
subject.setStatus(STATUS_ENABLED);
|
||||
subjectMapper.insert(subject);
|
||||
return subject;
|
||||
}
|
||||
|
||||
private ChannelAccount createWeComBotAccount(String tenantCode, Long subjectId) {
|
||||
ChannelAccount account = new ChannelAccount();
|
||||
account.setTenantCode(tenantCode);
|
||||
account.setAccountType(AccountType.WECOM_BOT.getCode());
|
||||
account.setChannelType(CHANNEL_TYPE_WECOM);
|
||||
account.setSubjectId(subjectId);
|
||||
account.setAccountId("bot-" + UUID.randomUUID().toString().substring(0, 8));
|
||||
account.setAccountName("企微群机器人");
|
||||
account.setStatus(STATUS_ENABLED);
|
||||
accountMapper.insert(account);
|
||||
return account;
|
||||
}
|
||||
|
||||
private ChannelAccount createWeComAgentAccount(String tenantCode, Long subjectId) {
|
||||
ChannelAccount account = new ChannelAccount();
|
||||
account.setTenantCode(tenantCode);
|
||||
account.setAccountType(AccountType.WECOM_AGENT.getCode());
|
||||
account.setChannelType(CHANNEL_TYPE_WECOM);
|
||||
account.setSubjectId(subjectId);
|
||||
account.setAccountId("agent-" + UUID.randomUUID().toString().substring(0, 8));
|
||||
account.setAccountName("企微应用");
|
||||
account.setStatus(STATUS_ENABLED);
|
||||
accountMapper.insert(account);
|
||||
return account;
|
||||
}
|
||||
|
||||
private ChannelAccount createWeChatPersonalAccount(String tenantCode, Long subjectId) {
|
||||
ChannelAccount account = new ChannelAccount();
|
||||
account.setTenantCode(tenantCode);
|
||||
account.setAccountType(AccountType.WECHAT_PERSONAL.getCode());
|
||||
account.setChannelType(CHANNEL_TYPE_WECHAT_PERSONAL);
|
||||
account.setSubjectId(subjectId);
|
||||
account.setAccountId("wxid-" + UUID.randomUUID().toString().substring(0, 8));
|
||||
account.setAccountName("pywechat 个人号");
|
||||
account.setStatus(STATUS_ENABLED);
|
||||
account.setOnlineStatus(STATUS_ENABLED);
|
||||
account.setRiskLevel(1);
|
||||
account.setLastHeartbeat(LocalDateTime.now());
|
||||
accountMapper.insert(account);
|
||||
return account;
|
||||
}
|
||||
|
||||
private Conversation createConversation(String tenantCode, Long subjectId,
|
||||
Integer conversationScene, String conversationName) {
|
||||
Conversation conversation = new Conversation();
|
||||
conversation.setTenantCode(tenantCode);
|
||||
conversation.setConversationType(2);
|
||||
conversation.setChannelType(CHANNEL_TYPE_WECOM);
|
||||
conversation.setChannelConversationId("chat-" + UUID.randomUUID().toString().substring(0, 8));
|
||||
conversation.setSubjectId(subjectId);
|
||||
conversation.setConversationName(conversationName);
|
||||
conversation.setConversationScene(conversationScene);
|
||||
conversation.setCreateSource(1);
|
||||
conversation.setStatus(STATUS_ENABLED);
|
||||
conversationMapper.insert(conversation);
|
||||
return conversation;
|
||||
}
|
||||
|
||||
private void bindAccountToConversation(Long accountId, Long conversationId) {
|
||||
ChannelAccountConversation binding = new ChannelAccountConversation();
|
||||
binding.setAccountId(accountId);
|
||||
binding.setConversationId(conversationId);
|
||||
binding.setBindingType(BINDING_TYPE_SEND_PRIMARY);
|
||||
binding.setSortOrder(1);
|
||||
binding.setStatus(STATUS_ENABLED);
|
||||
accountConversationMapper.insert(binding);
|
||||
}
|
||||
|
||||
private Person createPerson(String tenantCode, String personCode, String personName, Integer personType) {
|
||||
Person person = new Person();
|
||||
person.setTenantCode(tenantCode);
|
||||
person.setPersonCode(personCode + "-" + UUID.randomUUID().toString().substring(0, 8));
|
||||
person.setPersonName(personName);
|
||||
person.setPhone("13800138000");
|
||||
person.setPersonType(personType);
|
||||
person.setStatus(STATUS_ENABLED);
|
||||
personMapper.insert(person);
|
||||
return person;
|
||||
}
|
||||
|
||||
private void createChannelIdentity(String tenantCode, Long personId, Long subjectId, String channelUserId) {
|
||||
ChannelIdentity identity = new ChannelIdentity();
|
||||
identity.setTenantCode(tenantCode);
|
||||
identity.setPersonId(personId);
|
||||
identity.setChannelType(CHANNEL_TYPE_WECOM);
|
||||
identity.setChannelUserId(channelUserId + "-" + UUID.randomUUID().toString().substring(0, 8));
|
||||
identity.setChannelUserName("测试用户");
|
||||
identity.setSubjectId(subjectId);
|
||||
identity.setStatus(STATUS_ENABLED);
|
||||
channelIdentityMapper.insert(identity);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user