补充提交
parent
27ae0a3def
commit
ddd97e0514
|
|
@ -0,0 +1,160 @@
|
|||
package com.imeeting.service.biz.impl;
|
||||
|
||||
import com.imeeting.common.SysParamKeys;
|
||||
import com.imeeting.entity.biz.Meeting;
|
||||
import com.imeeting.entity.biz.PromptTemplate;
|
||||
import com.imeeting.service.biz.PromptTemplateService;
|
||||
import com.unisbase.service.SysParamService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class MeetingSummaryPromptAssembler {
|
||||
|
||||
public static final String PROMPT_SCHEMA_VERSION = "v2";
|
||||
private static final String SYSTEM_PROMPT_NOT_CONFIGURED_MESSAGE =
|
||||
"系统提示词未配置,请先维护系统参数 " + SysParamKeys.MEETING_SUMMARY_SYSTEM_PROMPT;
|
||||
|
||||
private final PromptTemplateService promptTemplateService;
|
||||
private final SysParamService sysParamService;
|
||||
|
||||
public Map<String, Object> buildTaskConfig(Long summaryModelId, Long promptId, String userPrompt) {
|
||||
Map<String, Object> taskConfig = new HashMap<>();
|
||||
taskConfig.put("summaryModelId", summaryModelId);
|
||||
taskConfig.put("promptSchemaVersion", PROMPT_SCHEMA_VERSION);
|
||||
taskConfig.put("effectiveSystemPrompt", resolveSystemPrompt());
|
||||
|
||||
String templatePrompt = resolveTemplatePrompt(promptId);
|
||||
taskConfig.put("effectiveTemplatePrompt", templatePrompt);
|
||||
taskConfig.put("userPrompt", normalizeOptionalText(userPrompt));
|
||||
|
||||
if (promptId != null) {
|
||||
taskConfig.put("promptId", promptId);
|
||||
}
|
||||
if (StringUtils.hasText(templatePrompt)) {
|
||||
taskConfig.put("promptContent", templatePrompt);
|
||||
}
|
||||
return taskConfig;
|
||||
}
|
||||
|
||||
public String buildSystemMessage(Map<String, Object> taskConfig) {
|
||||
String systemPrompt = stringValue(taskConfig, "effectiveSystemPrompt");
|
||||
if (!StringUtils.hasText(systemPrompt)) {
|
||||
systemPrompt = resolveSystemPrompt();
|
||||
}
|
||||
String templatePrompt = firstNonBlank(
|
||||
stringValue(taskConfig, "effectiveTemplatePrompt"),
|
||||
stringValue(taskConfig, "promptContent"),
|
||||
"请输出结构清晰、信息完整、适合直接阅读和导出的会议纪要。"
|
||||
);
|
||||
|
||||
return String.join("\n\n",
|
||||
"你是一名擅长中文会议纪要、结构化分析和待办提取的助手。",
|
||||
"系统提示词(基础边界,优先级最高):\n" + systemPrompt,
|
||||
"模板提示词(结构与风格要求):\n" + templatePrompt,
|
||||
"输出要求:",
|
||||
"1. 最终只能输出一个 JSON 对象,不要输出 Markdown 代码块、解释说明或额外前后缀。",
|
||||
"2. JSON 必须包含 `summaryContent` 和 `analysis` 两个顶级字段。",
|
||||
"3. `summaryContent` 必须是完整、自然、可直接保存和导出的正式会议纪要正文。",
|
||||
"4. `analysis` 仅作为结构化附加结果,不能替代 `summaryContent`。",
|
||||
"5. 如果系统提示词、模板提示词和用户提示词存在冲突,优先级为:系统提示词 > 模板提示词 > 用户提示词。"
|
||||
);
|
||||
}
|
||||
|
||||
public String buildUserMessage(Meeting meeting, String asrText, String userPrompt) {
|
||||
String participants = meeting.getParticipants() == null || meeting.getParticipants().isBlank()
|
||||
? "未填写"
|
||||
: meeting.getParticipants();
|
||||
String meetingTime = meeting.getMeetingTime() == null ? "未知" : meeting.getMeetingTime().toString();
|
||||
String normalizedUserPrompt = normalizeOptionalText(userPrompt);
|
||||
|
||||
StringBuilder message = new StringBuilder()
|
||||
.append("请基于以下会议转写内容生成会议纪要与结构化分析结果。\n")
|
||||
.append("会议信息:\n")
|
||||
.append("标题:").append(StringUtils.hasText(meeting.getTitle()) ? meeting.getTitle() : "未命名会议").append("\n")
|
||||
.append("会议时间:").append(meetingTime).append("\n")
|
||||
.append("参会人员:").append(participants).append("\n");
|
||||
|
||||
if (StringUtils.hasText(normalizedUserPrompt)) {
|
||||
message.append("\n")
|
||||
.append("用户提示词(仅用于补充关注重点,不得覆盖系统边界或模板结构要求):\n")
|
||||
.append(normalizedUserPrompt)
|
||||
.append("\n");
|
||||
}
|
||||
|
||||
message.append("\n")
|
||||
.append("返回 JSON,格式固定如下:\n")
|
||||
.append("{\n")
|
||||
.append(" \"summaryContent\": \"完整会议纪要正文,使用 markdown\",\n")
|
||||
.append(" \"analysis\": {\n")
|
||||
.append(" \"overview\": \"会议概览\",\n")
|
||||
.append(" \"keywords\": [\"关键词1\", \"关键词2\"],\n")
|
||||
.append(" \"chapters\": [{\"time\":\"00:00\",\"title\":\"章节标题\",\"summary\":\"章节摘要\"}],\n")
|
||||
.append(" \"speakerSummaries\": [{\"speaker\":\"发言人\",\"summary\":\"观点总结\"}],\n")
|
||||
.append(" \"keyPoints\": [{\"title\":\"关键点\",\"summary\":\"具体说明\",\"speaker\":\"发言人\"}],\n")
|
||||
.append(" \"todos\": [\"待办事项1\", \"待办事项2\"]\n")
|
||||
.append(" }\n")
|
||||
.append("}\n")
|
||||
.append("要求:\n")
|
||||
.append("1. `summaryContent` 必须优先遵循模板提示词中的结构、标题层级、章节顺序和写作风格。\n")
|
||||
.append("2. `analysis` 必须基于完整转写内容生成,不得脱离上下文。\n")
|
||||
.append("3. 若无待办事项,`todos` 返回空数组。\n")
|
||||
.append("4. 仅输出 JSON。\n")
|
||||
.append("\n")
|
||||
.append("会议转写如下:\n")
|
||||
.append(asrText == null ? "" : asrText);
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
public String resolveSystemPrompt() {
|
||||
String configured = sysParamService.getCachedParamValue(
|
||||
SysParamKeys.MEETING_SUMMARY_SYSTEM_PROMPT,
|
||||
""
|
||||
);
|
||||
String resolved = firstNonBlank(configured, null);
|
||||
if (!StringUtils.hasText(resolved)) {
|
||||
throw new RuntimeException(SYSTEM_PROMPT_NOT_CONFIGURED_MESSAGE);
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
public String resolveTemplatePrompt(Long promptId) {
|
||||
if (promptId == null) {
|
||||
return "";
|
||||
}
|
||||
PromptTemplate template = promptTemplateService.getById(promptId);
|
||||
if (template == null) {
|
||||
return "";
|
||||
}
|
||||
return firstNonBlank(template.getPromptContent(), "");
|
||||
}
|
||||
|
||||
public String normalizeOptionalText(String value) {
|
||||
return firstNonBlank(value, null);
|
||||
}
|
||||
|
||||
private String stringValue(Map<String, Object> source, String key) {
|
||||
if (source == null || key == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = source.get(key);
|
||||
return value == null ? null : normalizeOptionalText(String.valueOf(value));
|
||||
}
|
||||
|
||||
private String firstNonBlank(String... values) {
|
||||
if (values == null) {
|
||||
return null;
|
||||
}
|
||||
for (String value : values) {
|
||||
if (StringUtils.hasText(value)) {
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
package com.imeeting.controller.android.legacy;
|
||||
|
||||
import com.imeeting.dto.android.legacy.LegacyApiResponse;
|
||||
import com.imeeting.dto.android.legacy.LegacyLoginResponse;
|
||||
import com.imeeting.dto.android.legacy.LegacyRefreshTokenResponse;
|
||||
import com.unisbase.dto.LoginRequest;
|
||||
import com.unisbase.dto.RefreshRequest;
|
||||
import com.unisbase.dto.SysRoleDTO;
|
||||
import com.unisbase.dto.SysUserDTO;
|
||||
import com.unisbase.dto.TokenResponse;
|
||||
import com.unisbase.service.AuthService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class LegacyAuthControllerTest {
|
||||
|
||||
@Test
|
||||
void loginShouldReturnLegacyAndroidPayload() {
|
||||
AuthService authService = mock(AuthService.class);
|
||||
LegacyAuthController controller = new LegacyAuthController(authService);
|
||||
|
||||
LoginRequest request = new LoginRequest();
|
||||
request.setUsername("admin");
|
||||
request.setPassword("123456");
|
||||
|
||||
SysRoleDTO role = new SysRoleDTO();
|
||||
role.setRoleId(1L);
|
||||
role.setRoleName("超级管理员");
|
||||
|
||||
SysUserDTO user = new SysUserDTO();
|
||||
user.setUserId(1001L);
|
||||
user.setUsername("admin");
|
||||
user.setDisplayName("管理员");
|
||||
user.setAvatarUrl("https://avatar.example.com/a.png");
|
||||
user.setEmail("admin@example.com");
|
||||
user.setCreatedAt(LocalDateTime.of(2026, 4, 16, 10, 0));
|
||||
user.setRoles(List.of(role));
|
||||
|
||||
TokenResponse tokenResponse = TokenResponse.builder()
|
||||
.accessToken("access-token")
|
||||
.refreshToken("refresh-token")
|
||||
.user(user)
|
||||
.build();
|
||||
when(authService.login(request, true)).thenReturn(tokenResponse);
|
||||
|
||||
LegacyApiResponse<LegacyLoginResponse> response = controller.login(request);
|
||||
|
||||
verify(authService).login(request, true);
|
||||
assertEquals("200", response.getCode());
|
||||
assertNotNull(response.getData());
|
||||
assertEquals("access-token", response.getData().getToken());
|
||||
assertEquals(1001L, response.getData().getUser().getUser_id());
|
||||
assertEquals("admin", response.getData().getUser().getUsername());
|
||||
assertEquals("管理员", response.getData().getUser().getCaption());
|
||||
assertEquals("https://avatar.example.com/a.png", response.getData().getUser().getAvatar_url());
|
||||
assertEquals("admin@example.com", response.getData().getUser().getEmail());
|
||||
assertEquals(1L, response.getData().getUser().getRole_id());
|
||||
assertEquals("超级管理员", response.getData().getUser().getRole_name());
|
||||
assertEquals(LocalDateTime.of(2026, 4, 16, 10, 0), response.getData().getUser().getCreated_at());
|
||||
}
|
||||
|
||||
@Test
|
||||
void refreshShouldReturnLegacyAndroidPayload() {
|
||||
AuthService authService = mock(AuthService.class);
|
||||
LegacyAuthController controller = new LegacyAuthController(authService);
|
||||
|
||||
RefreshRequest request = new RefreshRequest();
|
||||
request.setRefreshToken("refresh-token");
|
||||
|
||||
TokenResponse tokenResponse = TokenResponse.builder()
|
||||
.accessToken("new-access-token")
|
||||
.refreshToken("new-refresh-token")
|
||||
.build();
|
||||
when(authService.refresh("refresh-token")).thenReturn(tokenResponse);
|
||||
|
||||
LegacyApiResponse<LegacyRefreshTokenResponse> response = controller.refresh(request, null, null);
|
||||
|
||||
verify(authService).refresh("refresh-token");
|
||||
assertEquals("200", response.getCode());
|
||||
assertNotNull(response.getData());
|
||||
assertEquals("new-access-token", response.getData().getToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
void refreshShouldSupportAuthorizationHeaderFallback() {
|
||||
AuthService authService = mock(AuthService.class);
|
||||
LegacyAuthController controller = new LegacyAuthController(authService);
|
||||
|
||||
TokenResponse tokenResponse = TokenResponse.builder()
|
||||
.accessToken("header-access-token")
|
||||
.build();
|
||||
when(authService.refresh("header-refresh-token")).thenReturn(tokenResponse);
|
||||
|
||||
LegacyApiResponse<LegacyRefreshTokenResponse> response = controller.refresh(null, "Bearer header-refresh-token", null);
|
||||
|
||||
verify(authService).refresh("header-refresh-token");
|
||||
assertEquals("200", response.getCode());
|
||||
assertNotNull(response.getData());
|
||||
assertEquals("header-access-token", response.getData().getToken());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
package com.imeeting.controller.android.legacy;
|
||||
|
||||
import com.imeeting.dto.android.legacy.LegacyApiResponse;
|
||||
import com.imeeting.dto.android.legacy.LegacyPromptListResponse;
|
||||
import com.imeeting.dto.biz.PromptTemplateVO;
|
||||
import com.imeeting.service.biz.PromptTemplateService;
|
||||
import com.unisbase.dto.PageResult;
|
||||
import com.unisbase.security.LoginUser;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class LegacyPromptControllerTest {
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
void activePromptsShouldReturnDescriptionForEnabledTemplates() {
|
||||
PromptTemplateService promptTemplateService = mock(PromptTemplateService.class);
|
||||
LegacyPromptController controller = new LegacyPromptController(promptTemplateService);
|
||||
|
||||
LoginUser loginUser = new LoginUser();
|
||||
loginUser.setTenantId(9L);
|
||||
loginUser.setUserId(7L);
|
||||
loginUser.setIsPlatformAdmin(false);
|
||||
loginUser.setIsTenantAdmin(false);
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new UsernamePasswordAuthenticationToken(loginUser, null, List.of())
|
||||
);
|
||||
|
||||
PromptTemplateVO enabledTemplate = new PromptTemplateVO();
|
||||
enabledTemplate.setId(1L);
|
||||
enabledTemplate.setTemplateName("标准模板");
|
||||
enabledTemplate.setDescription("适用于常规会议总结");
|
||||
enabledTemplate.setStatus(1);
|
||||
|
||||
PromptTemplateVO disabledTemplate = new PromptTemplateVO();
|
||||
disabledTemplate.setId(2L);
|
||||
disabledTemplate.setTemplateName("停用模板");
|
||||
disabledTemplate.setDescription("不应出现在结果中");
|
||||
disabledTemplate.setStatus(0);
|
||||
|
||||
PageResult<List<PromptTemplateVO>> pageResult = new PageResult<>();
|
||||
pageResult.setRecords(List.of(enabledTemplate, disabledTemplate));
|
||||
pageResult.setTotal(2L);
|
||||
|
||||
when(promptTemplateService.pageTemplates(eq(1), eq(1000), eq(null), eq(null), eq(9L), eq(7L), eq(false), eq(false)))
|
||||
.thenReturn(pageResult);
|
||||
|
||||
LegacyApiResponse<LegacyPromptListResponse> response = controller.activePrompts("MEETING_TASK");
|
||||
|
||||
assertEquals("200", response.getCode());
|
||||
assertNotNull(response.getData());
|
||||
assertEquals(1, response.getData().getPrompts().size());
|
||||
assertEquals("标准模板", response.getData().getPrompts().get(0).getName());
|
||||
assertEquals("适用于常规会议总结", response.getData().getPrompts().get(0).getDescription());
|
||||
assertEquals(1, response.getData().getPrompts().get(0).getIsDefault());
|
||||
}
|
||||
|
||||
@Test
|
||||
void activePromptsShouldRejectUnsupportedScene() {
|
||||
LegacyPromptController controller = new LegacyPromptController(mock(PromptTemplateService.class));
|
||||
|
||||
LegacyApiResponse<LegacyPromptListResponse> response = controller.activePrompts("OTHER");
|
||||
|
||||
assertEquals("400", response.getCode());
|
||||
assertNull(response.getData());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
package com.imeeting.service.biz.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.imeeting.dto.biz.ClientDownloadDTO;
|
||||
import com.imeeting.entity.biz.ClientDownload;
|
||||
import com.imeeting.mapper.biz.ClientDownloadMapper;
|
||||
import com.unisbase.security.LoginUser;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@Disabled("Requires MyBatis-Plus table metadata bootstrap; not suitable for mapper-only unit execution.")
|
||||
class ClientDownloadServiceImplTest {
|
||||
|
||||
@Test
|
||||
void listForAdminShouldNotAppendTenantFilter() {
|
||||
ClientDownloadMapper mapper = mock(ClientDownloadMapper.class);
|
||||
when(mapper.selectList(any())).thenReturn(List.of());
|
||||
ClientDownloadServiceImpl service = newService(mapper);
|
||||
|
||||
service.listForAdmin(loginUser(9L, 88L), "android", 1);
|
||||
|
||||
ArgumentCaptor<Wrapper<ClientDownload>> wrapperCaptor = ArgumentCaptor.forClass(Wrapper.class);
|
||||
verify(mapper).selectList(wrapperCaptor.capture());
|
||||
assertFalse(wrapperCaptor.getValue().getSqlSegment().toLowerCase().contains("tenant"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createShouldPersistAsGlobalAndClearLatestAcrossAllTenants() {
|
||||
ClientDownloadMapper mapper = mock(ClientDownloadMapper.class);
|
||||
when(mapper.update(isNull(), any(Wrapper.class))).thenReturn(1);
|
||||
when(mapper.insert(any(ClientDownload.class))).thenReturn(1);
|
||||
ClientDownloadServiceImpl service = newService(mapper);
|
||||
|
||||
ClientDownloadDTO dto = new ClientDownloadDTO();
|
||||
dto.setPlatformCode("android");
|
||||
dto.setVersion("1.0.0");
|
||||
dto.setDownloadUrl("https://download.example/app.apk");
|
||||
dto.setStatus(1);
|
||||
dto.setIsLatest(1);
|
||||
|
||||
ClientDownload created = service.create(dto, loginUser(7L, 123L));
|
||||
|
||||
ArgumentCaptor<ClientDownload> entityCaptor = ArgumentCaptor.forClass(ClientDownload.class);
|
||||
verify(mapper).insert(entityCaptor.capture());
|
||||
assertEquals(0L, created.getTenantId());
|
||||
assertEquals(0L, entityCaptor.getValue().getTenantId());
|
||||
assertEquals(7L, entityCaptor.getValue().getCreatedBy());
|
||||
|
||||
verify(mapper).update(isNull(), any(Wrapper.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateAndRemoveShouldIgnoreRecordTenant() {
|
||||
ClientDownloadMapper mapper = mock(ClientDownloadMapper.class);
|
||||
ClientDownload entity = new ClientDownload();
|
||||
entity.setId(5L);
|
||||
entity.setTenantId(999L);
|
||||
entity.setPlatformCode("android");
|
||||
entity.setVersion("1.0.0");
|
||||
entity.setStatus(1);
|
||||
entity.setIsLatest(0);
|
||||
when(mapper.selectById(5L)).thenReturn(entity);
|
||||
when(mapper.updateById(any(ClientDownload.class))).thenReturn(1);
|
||||
when(mapper.deleteById(any(ClientDownload.class))).thenReturn(1);
|
||||
ClientDownloadServiceImpl service = newService(mapper);
|
||||
|
||||
ClientDownloadDTO dto = new ClientDownloadDTO();
|
||||
dto.setVersion("2.0.0");
|
||||
|
||||
assertDoesNotThrow(() -> service.update(5L, dto, loginUser(7L, 1L)));
|
||||
assertEquals(0L, entity.getTenantId());
|
||||
assertEquals("2.0.0", entity.getVersion());
|
||||
|
||||
assertDoesNotThrow(() -> service.removeClient(5L, loginUser(7L, 1L)));
|
||||
verify(mapper).deleteById(any(ClientDownload.class));
|
||||
}
|
||||
|
||||
private ClientDownloadServiceImpl newService(ClientDownloadMapper mapper) {
|
||||
ClientDownloadServiceImpl service = new ClientDownloadServiceImpl();
|
||||
ReflectionTestUtils.setField(service, "baseMapper", mapper);
|
||||
return service;
|
||||
}
|
||||
|
||||
private LoginUser loginUser(Long userId, Long tenantId) {
|
||||
LoginUser loginUser = new LoginUser();
|
||||
loginUser.setUserId(userId);
|
||||
loginUser.setTenantId(tenantId);
|
||||
return loginUser;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
package com.imeeting.service.biz.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.imeeting.dto.biz.ExternalAppDTO;
|
||||
import com.imeeting.entity.biz.ExternalApp;
|
||||
import com.imeeting.mapper.biz.ExternalAppMapper;
|
||||
import com.unisbase.mapper.SysUserMapper;
|
||||
import com.unisbase.security.LoginUser;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@Disabled("Requires MyBatis-Plus table metadata bootstrap; not suitable for mapper-only unit execution.")
|
||||
class ExternalAppServiceImplTest {
|
||||
|
||||
@Test
|
||||
void listForAdminShouldNotAppendTenantFilter() {
|
||||
ExternalAppMapper mapper = mock(ExternalAppMapper.class);
|
||||
when(mapper.selectList(any())).thenReturn(List.of());
|
||||
ExternalAppServiceImpl service = newService(mapper);
|
||||
|
||||
service.listForAdmin(loginUser(9L, 88L), "web", 1);
|
||||
|
||||
ArgumentCaptor<Wrapper<ExternalApp>> wrapperCaptor = ArgumentCaptor.forClass(Wrapper.class);
|
||||
verify(mapper).selectList(wrapperCaptor.capture());
|
||||
assertFalse(wrapperCaptor.getValue().getSqlSegment().toLowerCase().contains("tenant"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createShouldPersistAsGlobal() {
|
||||
ExternalAppMapper mapper = mock(ExternalAppMapper.class);
|
||||
when(mapper.insert(any(ExternalApp.class))).thenReturn(1);
|
||||
ExternalAppServiceImpl service = newService(mapper);
|
||||
|
||||
ExternalAppDTO dto = new ExternalAppDTO();
|
||||
dto.setAppName("会议看板");
|
||||
dto.setAppType("web");
|
||||
dto.setStatus(1);
|
||||
|
||||
ExternalApp created = service.create(dto, loginUser(7L, 123L));
|
||||
|
||||
ArgumentCaptor<ExternalApp> entityCaptor = ArgumentCaptor.forClass(ExternalApp.class);
|
||||
verify(mapper).insert(entityCaptor.capture());
|
||||
assertEquals(0L, created.getTenantId());
|
||||
assertEquals(0L, entityCaptor.getValue().getTenantId());
|
||||
assertEquals(7L, entityCaptor.getValue().getCreatedBy());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateAndRemoveShouldIgnoreRecordTenant() {
|
||||
ExternalAppMapper mapper = mock(ExternalAppMapper.class);
|
||||
ExternalApp entity = new ExternalApp();
|
||||
entity.setId(8L);
|
||||
entity.setTenantId(999L);
|
||||
entity.setAppName("旧应用");
|
||||
entity.setAppType("web");
|
||||
entity.setStatus(1);
|
||||
when(mapper.selectById(8L)).thenReturn(entity);
|
||||
when(mapper.updateById(any(ExternalApp.class))).thenReturn(1);
|
||||
when(mapper.deleteById(any(ExternalApp.class))).thenReturn(1);
|
||||
ExternalAppServiceImpl service = newService(mapper);
|
||||
|
||||
ExternalAppDTO dto = new ExternalAppDTO();
|
||||
dto.setAppName("新应用");
|
||||
|
||||
assertDoesNotThrow(() -> service.update(8L, dto, loginUser(7L, 1L)));
|
||||
assertEquals(0L, entity.getTenantId());
|
||||
assertEquals("新应用", entity.getAppName());
|
||||
|
||||
assertDoesNotThrow(() -> service.removeApp(8L, loginUser(7L, 1L)));
|
||||
verify(mapper).deleteById(any(ExternalApp.class));
|
||||
}
|
||||
|
||||
private ExternalAppServiceImpl newService(ExternalAppMapper mapper) {
|
||||
ExternalAppServiceImpl service = new ExternalAppServiceImpl(mock(SysUserMapper.class));
|
||||
ReflectionTestUtils.setField(service, "baseMapper", mapper);
|
||||
return service;
|
||||
}
|
||||
|
||||
private LoginUser loginUser(Long userId, Long tenantId) {
|
||||
LoginUser loginUser = new LoginUser();
|
||||
loginUser.setUserId(userId);
|
||||
loginUser.setTenantId(tenantId);
|
||||
return loginUser;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.imeeting.service.biz.impl;
|
||||
|
||||
import com.imeeting.entity.biz.Meeting;
|
||||
import com.imeeting.mapper.biz.MeetingMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
class MeetingAccessServiceImplTest {
|
||||
|
||||
private final MeetingAccessServiceImpl service = new MeetingAccessServiceImpl(mock(MeetingMapper.class));
|
||||
|
||||
@Test
|
||||
void previewPasswordShouldBeOptionalWhenMeetingHasNoPassword() {
|
||||
Meeting meeting = new Meeting();
|
||||
meeting.setAccessPassword(" ");
|
||||
|
||||
assertFalse(service.isPreviewPasswordRequired(meeting));
|
||||
assertDoesNotThrow(() -> service.assertCanPreviewMeeting(meeting, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void previewPasswordShouldRejectMissingOrWrongPassword() {
|
||||
Meeting meeting = new Meeting();
|
||||
meeting.setAccessPassword("123456");
|
||||
|
||||
RuntimeException missingError = assertThrows(RuntimeException.class, () -> service.assertCanPreviewMeeting(meeting, null));
|
||||
assertEquals("Access password is required", missingError.getMessage());
|
||||
|
||||
RuntimeException wrongError = assertThrows(RuntimeException.class, () -> service.assertCanPreviewMeeting(meeting, "654321"));
|
||||
assertEquals("Access password is incorrect", wrongError.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void previewPasswordShouldAllowTrimmedMatch() {
|
||||
Meeting meeting = new Meeting();
|
||||
meeting.setAccessPassword(" 123456 ");
|
||||
|
||||
assertTrue(service.isPreviewPasswordRequired(meeting));
|
||||
assertDoesNotThrow(() -> service.assertCanPreviewMeeting(meeting, " 123456 "));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package com.imeeting.service.biz.impl;
|
||||
|
||||
import com.imeeting.common.SysParamKeys;
|
||||
import com.imeeting.entity.biz.Meeting;
|
||||
import com.imeeting.entity.biz.PromptTemplate;
|
||||
import com.imeeting.service.biz.PromptTemplateService;
|
||||
import com.unisbase.service.SysParamService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class MeetingSummaryPromptAssemblerTest {
|
||||
|
||||
@Test
|
||||
void buildTaskConfigShouldCaptureEffectivePromptsAndUserPrompt() {
|
||||
PromptTemplateService promptTemplateService = mock(PromptTemplateService.class);
|
||||
SysParamService sysParamService = mock(SysParamService.class);
|
||||
PromptTemplate template = new PromptTemplate();
|
||||
template.setPromptContent("模板提示词");
|
||||
when(promptTemplateService.getById(3L)).thenReturn(template);
|
||||
when(sysParamService.getCachedParamValue(eq(SysParamKeys.MEETING_SUMMARY_SYSTEM_PROMPT), eq("")))
|
||||
.thenReturn("系统提示词");
|
||||
|
||||
MeetingSummaryPromptAssembler assembler = new MeetingSummaryPromptAssembler(promptTemplateService, sysParamService);
|
||||
|
||||
Map<String, Object> taskConfig = assembler.buildTaskConfig(2L, 3L, " 关注风险项 ");
|
||||
|
||||
assertEquals(2L, taskConfig.get("summaryModelId"));
|
||||
assertEquals(3L, taskConfig.get("promptId"));
|
||||
assertEquals("v2", taskConfig.get("promptSchemaVersion"));
|
||||
assertEquals("系统提示词", taskConfig.get("effectiveSystemPrompt"));
|
||||
assertEquals("模板提示词", taskConfig.get("effectiveTemplatePrompt"));
|
||||
assertEquals("关注风险项", taskConfig.get("userPrompt"));
|
||||
assertEquals("模板提示词", taskConfig.get("promptContent"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildSystemMessageShouldFallbackToLegacyPromptContent() {
|
||||
SysParamService sysParamService = mock(SysParamService.class);
|
||||
when(sysParamService.getCachedParamValue(eq(SysParamKeys.MEETING_SUMMARY_SYSTEM_PROMPT), eq("")))
|
||||
.thenReturn("系统提示词");
|
||||
MeetingSummaryPromptAssembler assembler = new MeetingSummaryPromptAssembler(
|
||||
mock(PromptTemplateService.class),
|
||||
sysParamService
|
||||
);
|
||||
|
||||
String systemMessage = assembler.buildSystemMessage(Map.of("promptContent", "旧模板提示词"));
|
||||
|
||||
assertTrue(systemMessage.contains("旧模板提示词"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildUserMessageShouldOmitUserPromptSectionWhenBlank() {
|
||||
MeetingSummaryPromptAssembler assembler = new MeetingSummaryPromptAssembler(
|
||||
mock(PromptTemplateService.class),
|
||||
mock(SysParamService.class)
|
||||
);
|
||||
Meeting meeting = new Meeting();
|
||||
meeting.setTitle("周会");
|
||||
meeting.setMeetingTime(LocalDateTime.of(2026, 4, 16, 10, 0));
|
||||
meeting.setParticipants("张三,李四");
|
||||
|
||||
String userMessage = assembler.buildUserMessage(meeting, "这里是转写文本", " ");
|
||||
|
||||
assertFalse(userMessage.contains("用户提示词"));
|
||||
assertTrue(userMessage.contains("这里是转写文本"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveSystemPromptShouldFailWhenSystemParamMissing() {
|
||||
SysParamService sysParamService = mock(SysParamService.class);
|
||||
when(sysParamService.getCachedParamValue(eq(SysParamKeys.MEETING_SUMMARY_SYSTEM_PROMPT), eq("")))
|
||||
.thenReturn(" ");
|
||||
MeetingSummaryPromptAssembler assembler = new MeetingSummaryPromptAssembler(
|
||||
mock(PromptTemplateService.class),
|
||||
sysParamService
|
||||
);
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class, assembler::resolveSystemPrompt);
|
||||
|
||||
assertTrue(exception.getMessage().contains(SysParamKeys.MEETING_SUMMARY_SYSTEM_PROMPT));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue