Files

1 line
86 KiB
JSON

{"repo": "ayong-ui/verilearn", "files": {"verilearn/src/test/java/com/verilearn/infra/feishu/FeishuEventControllerTest.java": {"fa8cbb70ad107c1afd44a6a415eec7b82bc925ce": {"t": 1785746124.0015943, "n": 17129, "h": "bec1279d51d556e2", "c": "package com.verilearn.infra.feishu;\n\nimport com.verilearn.ai.dto.AiProviderConfigResponse;\nimport com.verilearn.ai.service.AiProviderConfigService;\nimport com.verilearn.chapter.dto.ChapterDemoEvaluationResponse;\nimport com.verilearn.chapter.dto.ChapterSummaryResponse;\nimport com.verilearn.infra.feishu.service.FeishuMessagingService;\nimport com.verilearn.progress.dto.ProgressResponse;\nimport com.verilearn.task.dto.TaskResponse;\nimport com.verilearn.workflow.dto.LearnerDashboardResponse;\nimport com.verilearn.workflow.dto.LearnerMaterialReference;\nimport com.verilearn.workflow.dto.LearnerSetupResponse;\nimport com.verilearn.workflow.service.LearnerWorkflowService;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.http.MediaType;\nimport org.springframework.test.web.servlet.MockMvc;\n\nimport java.util.List;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.contains;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;\n\n@SpringBootTest\n@AutoConfigureMockMvc\nclass FeishuEventControllerTest {\n\n @Autowired\n private MockMvc mockMvc;\n\n @MockBean\n private LearnerWorkflowService learnerWorkflowService;\n\n @MockBean\n private FeishuMessagingService feishuMessagingService;\n\n @MockBean\n private AiProviderConfigService aiProviderConfigService;\n\n @Test\n void shouldReturnChallengeForUrlVerification() throws Exception {\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(\"\"\"\n {\n \"type\": \"url_verification\",\n \"challenge\": \"challenge-token\"\n }\n \"\"\"))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.challenge\").value(\"challenge-token\"));\n }\n\n @Test\n void shouldRejectStartCommandWithoutTopic() throws Exception {\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_start_empty\", \"/start\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(false))\n .andExpect(jsonPath(\"$.data.command\").value(\"/start\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"请在 /start 后面输入学习主题\")));\n }\n\n @Test\n void shouldHandleStartCommand() throws Exception {\n LearnerSetupResponse response = new LearnerSetupResponse();\n response.setTopic(\"Java 后端\");\n response.setDailyMinutes(120);\n response.setInitializedNodeCount(4);\n response.setChapterCount(4);\n when(learnerWorkflowService.setupLearner(any())).thenReturn(response);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_start\", \"/start Java 后端\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/start\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"已为你初始化学习主题:Java 后端\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"已生成知识点:4 个\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"/today\")));\n\n verify(feishuMessagingService).sendTextMessage(eq(\"ou_test_start\"), contains(\"Java 后端\"));\n }\n\n @Test\n void shouldBlockStartWhenActiveRouteExists() throws Exception {\n LearnerDashboardResponse dashboard = new LearnerDashboardResponse();\n dashboard.setTopic(\"MySQL\");\n dashboard.setGoalStatus(\"ACTIVE\");\n TaskResponse todayTask = new TaskResponse();\n todayTask.setStepType(\"RUN_DEMO\");\n dashboard.setTodayTask(todayTask);\n var currentChapter = new com.verilearn.chapter.dto.ChapterDetailResponse();\n currentChapter.setTitle(\"索引基础\");\n dashboard.setCurrentChapter(currentChapter);\n when(learnerWorkflowService.getDashboard(eq(\"ou_test_start_blocked\"))).thenReturn(dashboard);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_start_blocked\", \"/start Redis\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(false))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"你当前还有未完成的学习任务\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"当前主题:MySQL\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"请先发送 /today\")));\n }\n\n @Test\n void shouldHandleTodayCommand() throws Exception {\n TaskResponse taskResponse = new TaskResponse();\n taskResponse.setGoalText(\"学习 Spring Boot fundamentals\");\n taskResponse.setChapterTitle(\"Spring Boot fundamentals\");\n taskResponse.setCurrentChapterNo(1);\n taskResponse.setTotalChapterCount(4);\n taskResponse.setStepType(\"READ_THEORY\");\n taskResponse.setStatus(\"PENDING\");\n taskResponse.setRouteViewUrl(\"http://localhost:8080/learning-routes/ou_test_today/view\");\n taskResponse.setTheoryViewUrl(\"/materials/101/view\");\n taskResponse.setDemoViewUrl(\"/materials/102/view\");\n taskResponse.setValidationItems(List.of());\n\n LearnerDashboardResponse dashboard = new LearnerDashboardResponse();\n dashboard.setTopic(\"Spring Boot fundamentals\");\n dashboard.setCurrentChapterNo(1);\n dashboard.setTotalChapterCount(4);\n dashboard.setChapters(List.of(\n chapter(1, \"Spring Boot fundamentals\", \"IN_PROGRESS\"),\n chapter(2, \"Spring MVC basics\", \"NOT_STARTED\")\n ));\n when(learnerWorkflowService.generateTodayTask(eq(\"ou_test_today\"))).thenReturn(taskResponse);\n when(learnerWorkflowService.getDashboard(eq(\"ou_test_today\"))).thenReturn(dashboard);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_today\", \"/today\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/today\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"今日任务\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"路线概览\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"完成 Demo\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"/submit-demo 我完成了\")));\n }\n\n @Test\n void shouldHandleProgressCommand() throws Exception {\n ProgressResponse progressResponse = new ProgressResponse();\n progressResponse.setTopic(\"Java 后端\");\n progressResponse.setGoalStatus(\"IN_PROGRESS\");\n progressResponse.setTargetLevel(\"intern\");\n progressResponse.setTotalNodes(4);\n progressResponse.setInProgressNodes(1);\n progressResponse.setPassedNodes(2);\n progressResponse.setNeedsRetryNodes(1);\n progressResponse.setTotalChapters(4);\n progressResponse.setInProgressChapters(1);\n progressResponse.setCompletedChapters(2);\n progressResponse.setPendingReviewChapters(1);\n when(learnerWorkflowService.getProgress(eq(\"ou_test_progress\"))).thenReturn(progressResponse);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_progress\", \"/progress\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/progress\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"当前主题:Java 后端\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"知识点进度\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"章节进度\")));\n }\n\n @Test\n void shouldHandleDashboardCommand() throws Exception {\n LearnerDashboardResponse dashboard = new LearnerDashboardResponse();\n dashboard.setTopic(\"Java 后端\");\n dashboard.setGoalStatus(\"IN_PROGRESS\");\n dashboard.setCurrentChapterNo(1);\n dashboard.setTotalChapterCount(4);\n dashboard.setPendingReviewCount(1);\n dashboard.setRouteViewUrl(\"/learning-routes/ou_test_dashboard/view\");\n dashboard.setCurrentMaterials(List.of(\n new LearnerMaterialReference(101L, \"THEORY_DOC\", \"理论文档\", \"path/theory.md\", \"/api/materials/101/content\", \"/materials/101/view\"),\n new LearnerMaterialReference(102L, \"DEMO_GUIDE\", \"Demo 任务\", \"path/demo-task.md\", \"/api/materials/102/content\", \"/materials/102/view\")\n ));\n when(learnerWorkflowService.getDashboard(eq(\"ou_test_dashboard\"))).thenReturn(dashboard);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_dashboard\", \"/dashboard\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/dashboard\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"当前主题:Java 后端\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"路线入口\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"当前材料\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"http://localhost:8080/materials/101/view\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"http://localhost:8080/materials/102/view\")));\n }\n\n @Test\n void shouldHandleSubmitDemoCommandWithCurrentContext() throws Exception {\n ChapterDemoEvaluationResponse response = new ChapterDemoEvaluationResponse();\n response.setUnderstandingLevel(\"HIGH\");\n response.setChapterStatus(\"IN_PROGRESS\");\n response.setEvaluationViewUrl(\"/materials/201/view\");\n response.setNextStepViewUrl(\"/materials/202/view\");\n when(learnerWorkflowService.evaluateCurrentDemoSubmission(eq(\"ou_test_submit_demo\"), any()))\n .thenReturn(response);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_submit_demo\", \"/submit-demo 我完成了 ping 接口,并理解了包扫描\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/submit-demo\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"Demo 评估已完成\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"掌握程度:HIGH\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"http://localhost:8080/materials/201/view\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"http://localhost:8080/materials/202/view\")));\n }\n\n @Test\n void shouldHandleAiCurrentCommand() throws Exception {\n AiProviderConfigResponse response = new AiProviderConfigResponse();\n response.setProviderType(\"DEEPSEEK\");\n response.setModelName(\"deepseek-chat\");\n response.setBaseUrl(\"https://api.deepseek.com\");\n response.setStatus(\"ACTIVE\");\n response.setSourceType(\"SYSTEM_DEFAULT\");\n response.setApiKeyMasked(\"sk-xxxx***yyyy\");\n when(aiProviderConfigService.getCurrentConfig(eq(\"ou_test_ai_current\"))).thenReturn(response);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_ai_current\", \"/ai current\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/ai\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"当前 AI 配置\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"DEEPSEEK\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"deepseek-chat\")));\n }\n\n @Test\n void shouldHandleAiSwitchCommand() throws Exception {\n AiProviderConfigResponse response = new AiProviderConfigResponse();\n response.setConfigId(2L);\n response.setProviderType(\"OPENAI\");\n response.setModelName(\"gpt-4.1-mini\");\n response.setBaseUrl(\"https://api.openai.com/v1\");\n response.setApiKeyMasked(\"sk-open***mini\");\n when(aiProviderConfigService.activateConfig(eq(\"ou_test_ai_switch\"), eq(2L))).thenReturn(response);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_ai_switch\", \"/ai switch 2\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"已切换当前 AI 模型\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"OPENAI\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"gpt-4.1-mini\")));\n }\n\n @Test\n void shouldHandleAiSwitchCommandWithInvalidConfigId() throws Exception {\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_ai_switch_invalid\", \"/ai switch abc\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(false))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"配置ID 必须是数字\")));\n }\n\n @Test\n void shouldHandleAiConfigCommand() throws Exception {\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_ai_config\", \"/ai config\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"AI 配置说明\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"http://localhost:8080/ai/provider-config-page?openId=ou_test_ai_config\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"API Key\")));\n }\n\n private String messageEvent(String openId, String text) {\n return \"\"\"\n {\n \"header\": {\n \"event_type\": \"im.message.receive_v1\"\n },\n \"event\": {\n \"sender\": {\n \"sender_id\": {\n \"open_id\": \"%s\"\n }\n },\n \"message\": {\n \"message_type\": \"text\",\n \"content\": \"{\\\\\"text\\\\\":\\\\\"%s\\\\\"}\"\n }\n }\n }\n \"\"\".formatted(openId, escapeForMessageContent(text));\n }\n\n private String escapeForMessageContent(String value) {\n return value.replace(\"\\\\\", \"\\\\\\\\\").replace(\"\\\"\", \"\\\\\\\"\");\n }\n\n private ChapterSummaryResponse chapter(int chapterNo, String title, String status) {\n ChapterSummaryResponse response = new ChapterSummaryResponse();\n response.setChapterNo(chapterNo);\n response.setTitle(title);\n response.setStatus(status);\n return response;\n }\n}\n"}, "1b7c9359c834789f65f4778a627c356aaf4d8d2f": {"t": 1785760023.4777253, "n": 17325, "h": "00ebc2aa3e6edf1a", "c": "package com.verilearn.infra.feishu;\n\nimport com.verilearn.ai.dto.AiProviderConfigResponse;\nimport com.verilearn.ai.service.AiProviderConfigService;\nimport com.verilearn.chapter.dto.ChapterDemoEvaluationResponse;\nimport com.verilearn.chapter.dto.ChapterSummaryResponse;\nimport com.verilearn.infra.feishu.service.FeishuMessagingService;\nimport com.verilearn.progress.dto.ProgressResponse;\nimport com.verilearn.task.dto.TaskResponse;\nimport com.verilearn.workflow.dto.LearnerDashboardResponse;\nimport com.verilearn.workflow.dto.LearnerMaterialReference;\nimport com.verilearn.workflow.dto.LearnerSetupResponse;\nimport com.verilearn.workflow.service.LearnerWorkflowService;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.http.MediaType;\nimport org.springframework.test.web.servlet.MockMvc;\n\nimport java.util.List;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.contains;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;\n\n@SpringBootTest\n@AutoConfigureMockMvc\nclass FeishuEventControllerTest {\n\n @Autowired\n private MockMvc mockMvc;\n\n @MockBean\n private LearnerWorkflowService learnerWorkflowService;\n\n @MockBean\n private FeishuMessagingService feishuMessagingService;\n\n @MockBean\n private AiProviderConfigService aiProviderConfigService;\n\n @Test\n void shouldReturnChallengeForUrlVerification() throws Exception {\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(\"\"\"\n {\n \"type\": \"url_verification\",\n \"challenge\": \"challenge-token\"\n }\n \"\"\"))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.challenge\").value(\"challenge-token\"));\n }\n\n @Test\n void shouldRejectStartCommandWithoutTopic() throws Exception {\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_start_empty\", \"/start\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(false))\n .andExpect(jsonPath(\"$.data.command\").value(\"/start\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"请在 /start 后面输入学习主题\")));\n }\n\n @Test\n void shouldHandleStartCommand() throws Exception {\n LearnerSetupResponse response = new LearnerSetupResponse();\n response.setTopic(\"Java 后端\");\n response.setDailyMinutes(120);\n response.setInitializedNodeCount(4);\n response.setChapterCount(4);\n when(learnerWorkflowService.setupLearner(any())).thenReturn(response);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_start\", \"/start Java 后端\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/start\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"已为你初始化学习主题:Java 后端\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"已生成知识点:4 个\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"/today\")));\n\n verify(feishuMessagingService).sendTextMessage(eq(\"ou_test_start\"), contains(\"Java 后端\"));\n }\n\n @Test\n void shouldBlockStartWhenActiveRouteExists() throws Exception {\n LearnerDashboardResponse dashboard = new LearnerDashboardResponse();\n dashboard.setTopic(\"MySQL\");\n dashboard.setGoalStatus(\"ACTIVE\");\n TaskResponse todayTask = new TaskResponse();\n todayTask.setStepType(\"READ_THEORY\");\n dashboard.setTodayTask(todayTask);\n var currentChapter = new com.verilearn.chapter.dto.ChapterDetailResponse();\n currentChapter.setTitle(\"索引基础\");\n dashboard.setCurrentChapter(currentChapter);\n when(learnerWorkflowService.getDashboard(eq(\"ou_test_start_blocked\"))).thenReturn(dashboard);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_start_blocked\", \"/start Redis\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(false))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"你当前还有未完成的学习任务\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"当前主题:MySQL\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"请先发送 /today\")));\n }\n\n @Test\n void shouldHandleTodayCommand() throws Exception {\n TaskResponse taskResponse = new TaskResponse();\n taskResponse.setGoalText(\"学习 Spring Boot fundamentals\");\n taskResponse.setChapterTitle(\"Spring Boot fundamentals\");\n taskResponse.setCurrentChapterNo(1);\n taskResponse.setTotalChapterCount(4);\n taskResponse.setStepType(\"READ_THEORY\");\n taskResponse.setStatus(\"PENDING\");\n taskResponse.setRouteViewUrl(\"http://localhost:8080/learning-routes/ou_test_today/view\");\n taskResponse.setTheoryViewUrl(\"/materials/101/view\");\n taskResponse.setDemoViewUrl(\"/materials/102/view\");\n taskResponse.setValidationItems(List.of());\n\n LearnerDashboardResponse dashboard = new LearnerDashboardResponse();\n dashboard.setTopic(\"Spring Boot fundamentals\");\n dashboard.setCurrentChapterNo(1);\n dashboard.setTotalChapterCount(4);\n dashboard.setChapters(List.of(\n chapter(1, \"Spring Boot fundamentals\", \"IN_PROGRESS\"),\n chapter(2, \"Spring MVC basics\", \"NOT_STARTED\")\n ));\n when(learnerWorkflowService.generateTodayTask(eq(\"ou_test_today\"))).thenReturn(taskResponse);\n when(learnerWorkflowService.getDashboard(eq(\"ou_test_today\"))).thenReturn(dashboard);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_today\", \"/today\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/today\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"今日任务\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"路线概览\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"先看理论\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"再做 Demo\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"本次验证项\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"/submit-demo 我完成了\")));\n }\n\n @Test\n void shouldHandleProgressCommand() throws Exception {\n ProgressResponse progressResponse = new ProgressResponse();\n progressResponse.setTopic(\"Java 后端\");\n progressResponse.setGoalStatus(\"IN_PROGRESS\");\n progressResponse.setTargetLevel(\"intern\");\n progressResponse.setTotalNodes(4);\n progressResponse.setInProgressNodes(1);\n progressResponse.setPassedNodes(2);\n progressResponse.setNeedsRetryNodes(1);\n progressResponse.setTotalChapters(4);\n progressResponse.setInProgressChapters(1);\n progressResponse.setCompletedChapters(2);\n progressResponse.setPendingReviewChapters(1);\n when(learnerWorkflowService.getProgress(eq(\"ou_test_progress\"))).thenReturn(progressResponse);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_progress\", \"/progress\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/progress\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"当前主题:Java 后端\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"知识点进度\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"章节进度\")));\n }\n\n @Test\n void shouldHandleDashboardCommand() throws Exception {\n LearnerDashboardResponse dashboard = new LearnerDashboardResponse();\n dashboard.setTopic(\"Java 后端\");\n dashboard.setGoalStatus(\"IN_PROGRESS\");\n dashboard.setCurrentChapterNo(1);\n dashboard.setTotalChapterCount(4);\n dashboard.setPendingReviewCount(1);\n dashboard.setRouteViewUrl(\"/learning-routes/ou_test_dashboard/view\");\n dashboard.setCurrentMaterials(List.of(\n new LearnerMaterialReference(101L, \"THEORY_DOC\", \"理论文档\", \"path/theory.md\", \"/api/materials/101/content\", \"/materials/101/view\"),\n new LearnerMaterialReference(102L, \"DEMO_GUIDE\", \"Demo 任务\", \"path/demo-task.md\", \"/api/materials/102/content\", \"/materials/102/view\")\n ));\n when(learnerWorkflowService.getDashboard(eq(\"ou_test_dashboard\"))).thenReturn(dashboard);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_dashboard\", \"/dashboard\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/dashboard\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"当前主题:Java 后端\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"路线入口\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"当前材料\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"http://localhost:8080/materials/101/view\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"http://localhost:8080/materials/102/view\")));\n }\n\n @Test\n void shouldHandleSubmitDemoCommandWithCurrentContext() throws Exception {\n ChapterDemoEvaluationResponse response = new ChapterDemoEvaluationResponse();\n response.setUnderstandingLevel(\"HIGH\");\n response.setChapterStatus(\"IN_PROGRESS\");\n response.setEvaluationViewUrl(\"/materials/201/view\");\n response.setNextStepViewUrl(\"/materials/202/view\");\n when(learnerWorkflowService.evaluateCurrentDemoSubmission(eq(\"ou_test_submit_demo\"), any()))\n .thenReturn(response);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_submit_demo\", \"/submit-demo 我完成了 ping 接口,并理解了包扫描\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/submit-demo\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"Demo 评估已完成\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"掌握程度:HIGH\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"http://localhost:8080/materials/201/view\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"http://localhost:8080/materials/202/view\")));\n }\n\n @Test\n void shouldHandleAiCurrentCommand() throws Exception {\n AiProviderConfigResponse response = new AiProviderConfigResponse();\n response.setProviderType(\"DEEPSEEK\");\n response.setModelName(\"deepseek-chat\");\n response.setBaseUrl(\"https://api.deepseek.com\");\n response.setStatus(\"ACTIVE\");\n response.setSourceType(\"SYSTEM_DEFAULT\");\n response.setApiKeyMasked(\"sk-xxxx***yyyy\");\n when(aiProviderConfigService.getCurrentConfig(eq(\"ou_test_ai_current\"))).thenReturn(response);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_ai_current\", \"/ai current\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/ai\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"当前 AI 配置\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"DEEPSEEK\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"deepseek-chat\")));\n }\n\n @Test\n void shouldHandleAiSwitchCommand() throws Exception {\n AiProviderConfigResponse response = new AiProviderConfigResponse();\n response.setConfigId(2L);\n response.setProviderType(\"OPENAI\");\n response.setModelName(\"gpt-4.1-mini\");\n response.setBaseUrl(\"https://api.openai.com/v1\");\n response.setApiKeyMasked(\"sk-open***mini\");\n when(aiProviderConfigService.activateConfig(eq(\"ou_test_ai_switch\"), eq(2L))).thenReturn(response);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_ai_switch\", \"/ai switch 2\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"已切换当前 AI 模型\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"OPENAI\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"gpt-4.1-mini\")));\n }\n\n @Test\n void shouldHandleAiSwitchCommandWithInvalidConfigId() throws Exception {\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_ai_switch_invalid\", \"/ai switch abc\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(false))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"配置ID 必须是数字\")));\n }\n\n @Test\n void shouldHandleAiConfigCommand() throws Exception {\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_ai_config\", \"/ai config\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"AI 配置说明\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"http://localhost:8080/ai/provider-config-page?openId=ou_test_ai_config\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"API Key\")));\n }\n\n private String messageEvent(String openId, String text) {\n return \"\"\"\n {\n \"header\": {\n \"event_type\": \"im.message.receive_v1\"\n },\n \"event\": {\n \"sender\": {\n \"sender_id\": {\n \"open_id\": \"%s\"\n }\n },\n \"message\": {\n \"message_type\": \"text\",\n \"content\": \"{\\\\\"text\\\\\":\\\\\"%s\\\\\"}\"\n }\n }\n }\n \"\"\".formatted(openId, escapeForMessageContent(text));\n }\n\n private String escapeForMessageContent(String value) {\n return value.replace(\"\\\\\", \"\\\\\\\\\").replace(\"\\\"\", \"\\\\\\\"\");\n }\n\n private ChapterSummaryResponse chapter(int chapterNo, String title, String status) {\n ChapterSummaryResponse response = new ChapterSummaryResponse();\n response.setChapterNo(chapterNo);\n response.setTitle(title);\n response.setStatus(status);\n return response;\n }\n}\n"}, "efef66f528f128dfb15e0d577bfbee0b38e398eb": {"t": 1785760025.2154334, "n": 14249, "h": "f476f91ca926f84b", "c": "package com.verilearn.infra.feishu;\n\nimport com.verilearn.ai.dto.AiProviderConfigResponse;\nimport com.verilearn.ai.service.AiProviderConfigService;\nimport com.verilearn.chapter.dto.ChapterDemoEvaluationResponse;\nimport com.verilearn.chapter.dto.ChapterSummaryResponse;\nimport com.verilearn.infra.feishu.service.FeishuMessagingService;\nimport com.verilearn.progress.dto.ProgressResponse;\nimport com.verilearn.task.dto.TaskResponse;\nimport com.verilearn.workflow.dto.LearnerDashboardResponse;\nimport com.verilearn.workflow.dto.LearnerMaterialReference;\nimport com.verilearn.workflow.dto.LearnerSetupResponse;\nimport com.verilearn.workflow.service.LearnerWorkflowService;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.http.MediaType;\nimport org.springframework.test.web.servlet.MockMvc;\n\nimport java.util.List;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.contains;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;\n\n@SpringBootTest\n@AutoConfigureMockMvc\nclass FeishuEventControllerTest {\n\n @Autowired\n private MockMvc mockMvc;\n\n @MockBean\n private LearnerWorkflowService learnerWorkflowService;\n\n @MockBean\n private FeishuMessagingService feishuMessagingService;\n\n @MockBean\n private AiProviderConfigService aiProviderConfigService;\n\n @Test\n void shouldReturnChallengeForUrlVerification() throws Exception {\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(\"\"\"\n {\n \"type\": \"url_verification\",\n \"challenge\": \"challenge-token\"\n }\n \"\"\"))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.challenge\").value(\"challenge-token\"));\n }\n\n @Test\n void shouldHandleStartCommand() throws Exception {\n LearnerSetupResponse response = new LearnerSetupResponse();\n response.setTopic(\"Java 后端\");\n response.setDailyMinutes(120);\n response.setInitializedNodeCount(4);\n response.setChapterCount(4);\n when(learnerWorkflowService.setupLearner(any())).thenReturn(response);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_start\", \"/start Java 后端\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/start\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"Java 后端\")));\n\n verify(feishuMessagingService).sendTextMessage(eq(\"ou_test_start\"), contains(\"Java 后端\"));\n }\n\n @Test\n void shouldHandleTodayCommand() throws Exception {\n TaskResponse taskResponse = new TaskResponse();\n taskResponse.setGoalText(\"学习 Spring Boot fundamentals\");\n taskResponse.setChapterTitle(\"Spring Boot fundamentals\");\n taskResponse.setCurrentChapterNo(1);\n taskResponse.setTotalChapterCount(4);\n taskResponse.setStepType(\"READ_THEORY\");\n taskResponse.setStatus(\"PENDING\");\n taskResponse.setRouteViewUrl(\"http://localhost:8080/learning-routes/ou_test_today/view\");\n taskResponse.setTheoryFilePath(\"spring-boot/01-spring-boot-fundamentals/user/theory/theory.md\");\n taskResponse.setTheoryContentUrl(\"/api/materials/101/content\");\n taskResponse.setTheoryViewUrl(\"/materials/101/view\");\n taskResponse.setDemoFilePath(\"spring-boot/01-spring-boot-fundamentals/user/demo/demo-task.md\");\n taskResponse.setDemoContentUrl(\"/api/materials/102/content\");\n taskResponse.setDemoViewUrl(\"/materials/102/view\");\n taskResponse.setValidationItems(List.of());\n\n LearnerDashboardResponse dashboard = new LearnerDashboardResponse();\n dashboard.setTopic(\"Spring Boot fundamentals\");\n dashboard.setChapterCount(4);\n dashboard.setChapters(List.of(\n chapter(1, \"Spring Boot fundamentals\", \"IN_PROGRESS\"),\n chapter(2, \"Spring MVC basics\", \"NOT_STARTED\")\n ));\n when(learnerWorkflowService.generateTodayTask(eq(\"ou_test_today\"))).thenReturn(taskResponse);\n when(learnerWorkflowService.getDashboard(eq(\"ou_test_today\"))).thenReturn(dashboard);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_today\", \"/today\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/today\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"学习 Spring Boot fundamentals\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"先看理论\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"http://localhost:8080/materials/101/view\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"再做 Demo\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"http://localhost:8080/materials/102/view\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"/submit-demo\")));\n }\n\n @Test\n void shouldHandleProgressCommand() throws Exception {\n ProgressResponse progressResponse = new ProgressResponse();\n progressResponse.setTopic(\"Java 后端\");\n progressResponse.setTotalNodes(4);\n progressResponse.setInProgressNodes(1);\n progressResponse.setPassedNodes(2);\n progressResponse.setNeedsRetryNodes(1);\n progressResponse.setTotalChapters(4);\n progressResponse.setInProgressChapters(1);\n progressResponse.setCompletedChapters(2);\n progressResponse.setPendingReviewChapters(1);\n when(learnerWorkflowService.getProgress(eq(\"ou_test_progress\"))).thenReturn(progressResponse);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_progress\", \"/progress\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/progress\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"Java 后端\")));\n }\n\n @Test\n void shouldHandleDashboardCommand() throws Exception {\n LearnerDashboardResponse dashboard = new LearnerDashboardResponse();\n dashboard.setTopic(\"Java 后端\");\n dashboard.setChapterCount(4);\n dashboard.setPendingReviewCount(1);\n TaskResponse taskResponse = new TaskResponse();\n taskResponse.setGoalText(\"学习 Spring Boot fundamentals\");\n dashboard.setTodayTask(taskResponse);\n dashboard.setCurrentMaterials(List.of(\n new LearnerMaterialReference(101L, \"THEORY_DOC\", \"理论文档\", \"path/theory.md\", \"/api/materials/101/content\", \"/materials/101/view\"),\n new LearnerMaterialReference(102L, \"DEMO_GUIDE\", \"Demo 任务\", \"path/demo-task.md\", \"/api/materials/102/content\", \"/materials/102/view\")\n ));\n when(learnerWorkflowService.getDashboard(eq(\"ou_test_dashboard\"))).thenReturn(dashboard);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_dashboard\", \"/dashboard\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/dashboard\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"Java 后端\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"http://localhost:8080/materials/101/view\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"http://localhost:8080/materials/102/view\")));\n }\n\n @Test\n void shouldHandleSubmitDemoCommandWithCurrentContext() throws Exception {\n ChapterDemoEvaluationResponse response = new ChapterDemoEvaluationResponse();\n response.setUnderstandingLevel(\"HIGH\");\n response.setChapterStatus(\"IN_PROGRESS\");\n response.setEvaluationContentUrl(\"/api/materials/201/content\");\n response.setEvaluationViewUrl(\"/materials/201/view\");\n response.setNextStepContentUrl(\"/api/materials/202/content\");\n response.setNextStepViewUrl(\"/materials/202/view\");\n when(learnerWorkflowService.evaluateCurrentDemoSubmission(eq(\"ou_test_submit_demo\"), any()))\n .thenReturn(response);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_submit_demo\", \"/submit-demo 我完成了 ping 接口,并理解了包扫描\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/submit-demo\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"http://localhost:8080/materials/201/view\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"http://localhost:8080/materials/202/view\")));\n }\n\n @Test\n void shouldHandleAiCurrentCommand() throws Exception {\n AiProviderConfigResponse response = new AiProviderConfigResponse();\n response.setProviderType(\"DEEPSEEK\");\n response.setModelName(\"deepseek-chat\");\n response.setBaseUrl(\"https://api.deepseek.com\");\n response.setStatus(\"ACTIVE\");\n response.setSourceType(\"SYSTEM_DEFAULT\");\n response.setApiKeyMasked(\"sk-xxxx***yyyy\");\n when(aiProviderConfigService.getCurrentConfig(eq(\"ou_test_ai_current\"))).thenReturn(response);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_ai_current\", \"/ai current\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/ai\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"DEEPSEEK\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"deepseek-chat\")));\n }\n\n @Test\n void shouldHandleAiSwitchCommand() throws Exception {\n AiProviderConfigResponse response = new AiProviderConfigResponse();\n response.setConfigId(2L);\n response.setProviderType(\"OPENAI\");\n response.setModelName(\"gpt-4.1-mini\");\n response.setBaseUrl(\"https://api.openai.com/v1\");\n response.setApiKeyMasked(\"sk-open***mini\");\n when(aiProviderConfigService.activateConfig(eq(\"ou_test_ai_switch\"), eq(2L))).thenReturn(response);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_ai_switch\", \"/ai switch 2\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"OPENAI\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"gpt-4.1-mini\")));\n }\n\n @Test\n void shouldHandleAiConfigCommand() throws Exception {\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_ai_config\", \"/ai config\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"http://localhost:8080/ai/provider-config-page?openId=ou_test_ai_config\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"API Key\")));\n }\n\n private String messageEvent(String openId, String text) {\n return \"\"\"\n {\n \"header\": {\n \"event_type\": \"im.message.receive_v1\"\n },\n \"event\": {\n \"sender\": {\n \"sender_id\": {\n \"open_id\": \"%s\"\n }\n },\n \"message\": {\n \"message_type\": \"text\",\n \"content\": \"{\\\\\"text\\\\\":\\\\\"%s\\\\\"}\"\n }\n }\n }\n \"\"\".formatted(openId, escapeForMessageContent(text));\n }\n\n private String escapeForMessageContent(String value) {\n return value.replace(\"\\\\\", \"\\\\\\\\\").replace(\"\\\"\", \"\\\\\\\"\");\n }\n\n private ChapterSummaryResponse chapter(int chapterNo, String title, String status) {\n ChapterSummaryResponse response = new ChapterSummaryResponse();\n response.setChapterNo(chapterNo);\n response.setTitle(title);\n response.setStatus(status);\n return response;\n }\n}\n"}, "372ec20c10f28ac0e283403f65ddf308c3f5856c": {"t": 1785760026.6582003, "n": 13110, "h": "b48e2d818fd35db7", "c": "package com.verilearn.infra.feishu;\n\nimport com.verilearn.ai.dto.AiProviderConfigResponse;\nimport com.verilearn.ai.service.AiProviderConfigService;\nimport com.verilearn.chapter.dto.ChapterDemoEvaluationResponse;\nimport com.verilearn.infra.feishu.service.FeishuMessagingService;\nimport com.verilearn.progress.dto.ProgressResponse;\nimport com.verilearn.task.dto.TaskResponse;\nimport com.verilearn.workflow.dto.LearnerDashboardResponse;\nimport com.verilearn.workflow.dto.LearnerMaterialReference;\nimport com.verilearn.workflow.dto.LearnerSetupResponse;\nimport com.verilearn.workflow.service.LearnerWorkflowService;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.http.MediaType;\nimport org.springframework.test.web.servlet.MockMvc;\n\nimport java.util.List;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.contains;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;\n\n@SpringBootTest\n@AutoConfigureMockMvc\nclass FeishuEventControllerTest {\n\n @Autowired\n private MockMvc mockMvc;\n\n @MockBean\n private LearnerWorkflowService learnerWorkflowService;\n\n @MockBean\n private FeishuMessagingService feishuMessagingService;\n\n @MockBean\n private AiProviderConfigService aiProviderConfigService;\n\n @Test\n void shouldReturnChallengeForUrlVerification() throws Exception {\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(\"\"\"\n {\n \"type\": \"url_verification\",\n \"challenge\": \"challenge-token\"\n }\n \"\"\"))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.challenge\").value(\"challenge-token\"));\n }\n\n @Test\n void shouldHandleStartCommand() throws Exception {\n LearnerSetupResponse response = new LearnerSetupResponse();\n response.setTopic(\"Java 后端\");\n response.setDailyMinutes(120);\n response.setInitializedNodeCount(4);\n response.setChapterCount(4);\n when(learnerWorkflowService.setupLearner(any())).thenReturn(response);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_start\", \"/start Java 后端\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/start\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"Java 后端\")));\n\n verify(feishuMessagingService).sendTextMessage(eq(\"ou_test_start\"), contains(\"Java 后端\"));\n }\n\n @Test\n void shouldHandleTodayCommand() throws Exception {\n TaskResponse taskResponse = new TaskResponse();\n taskResponse.setGoalText(\"学习 Spring Boot fundamentals\");\n taskResponse.setChapterTitle(\"Spring Boot fundamentals\");\n taskResponse.setStepType(\"READ_THEORY\");\n taskResponse.setStatus(\"PENDING\");\n taskResponse.setTheoryFilePath(\"spring-boot/01-spring-boot-fundamentals/user/theory/theory.md\");\n taskResponse.setTheoryContentUrl(\"/api/materials/101/content\");\n taskResponse.setTheoryViewUrl(\"/materials/101/view\");\n taskResponse.setDemoFilePath(\"spring-boot/01-spring-boot-fundamentals/user/demo/demo-task.md\");\n taskResponse.setDemoContentUrl(\"/api/materials/102/content\");\n taskResponse.setDemoViewUrl(\"/materials/102/view\");\n taskResponse.setValidationItems(List.of());\n when(learnerWorkflowService.generateTodayTask(eq(\"ou_test_today\"))).thenReturn(taskResponse);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_today\", \"/today\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/today\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"学习 Spring Boot fundamentals\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"theory.md\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"/materials/101/view\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"demo-task.md\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"/materials/102/view\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"/submit-demo\")));\n }\n\n @Test\n void shouldHandleProgressCommand() throws Exception {\n ProgressResponse progressResponse = new ProgressResponse();\n progressResponse.setTopic(\"Java 后端\");\n progressResponse.setTotalNodes(4);\n progressResponse.setInProgressNodes(1);\n progressResponse.setPassedNodes(2);\n progressResponse.setNeedsRetryNodes(1);\n progressResponse.setTotalChapters(4);\n progressResponse.setInProgressChapters(1);\n progressResponse.setCompletedChapters(2);\n progressResponse.setPendingReviewChapters(1);\n when(learnerWorkflowService.getProgress(eq(\"ou_test_progress\"))).thenReturn(progressResponse);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_progress\", \"/progress\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/progress\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"Java 后端\")));\n }\n\n @Test\n void shouldHandleDashboardCommand() throws Exception {\n LearnerDashboardResponse dashboard = new LearnerDashboardResponse();\n dashboard.setTopic(\"Java 后端\");\n dashboard.setChapterCount(4);\n dashboard.setPendingReviewCount(1);\n TaskResponse taskResponse = new TaskResponse();\n taskResponse.setGoalText(\"学习 Spring Boot fundamentals\");\n dashboard.setTodayTask(taskResponse);\n dashboard.setCurrentMaterials(List.of(\n new LearnerMaterialReference(101L, \"THEORY_DOC\", \"理论文档\", \"path/theory.md\", \"/api/materials/101/content\", \"/materials/101/view\"),\n new LearnerMaterialReference(102L, \"DEMO_GUIDE\", \"Demo 任务\", \"path/demo-task.md\", \"/api/materials/102/content\", \"/materials/102/view\")\n ));\n when(learnerWorkflowService.getDashboard(eq(\"ou_test_dashboard\"))).thenReturn(dashboard);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_dashboard\", \"/dashboard\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/dashboard\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"Java 后端\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"/materials/101/view\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"/materials/102/view\")));\n }\n\n @Test\n void shouldHandleSubmitDemoCommandWithCurrentContext() throws Exception {\n ChapterDemoEvaluationResponse response = new ChapterDemoEvaluationResponse();\n response.setUnderstandingLevel(\"HIGH\");\n response.setChapterStatus(\"IN_PROGRESS\");\n response.setEvaluationContentUrl(\"/api/materials/201/content\");\n response.setEvaluationViewUrl(\"/materials/201/view\");\n response.setNextStepContentUrl(\"/api/materials/202/content\");\n response.setNextStepViewUrl(\"/materials/202/view\");\n when(learnerWorkflowService.evaluateCurrentDemoSubmission(eq(\"ou_test_submit_demo\"), any()))\n .thenReturn(response);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_submit_demo\", \"/submit-demo 我完成了 ping 接口,并理解了包扫描\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/submit-demo\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"/materials/201/view\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"/materials/202/view\")));\n }\n\n @Test\n void shouldHandleAiCurrentCommand() throws Exception {\n AiProviderConfigResponse response = new AiProviderConfigResponse();\n response.setProviderType(\"DEEPSEEK\");\n response.setModelName(\"deepseek-chat\");\n response.setBaseUrl(\"https://api.deepseek.com\");\n response.setStatus(\"ACTIVE\");\n response.setSourceType(\"SYSTEM_DEFAULT\");\n response.setApiKeyMasked(\"sk-xxxx***yyyy\");\n when(aiProviderConfigService.getCurrentConfig(eq(\"ou_test_ai_current\"))).thenReturn(response);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_ai_current\", \"/ai current\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/ai\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"DEEPSEEK\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"deepseek-chat\")));\n }\n\n @Test\n void shouldHandleAiSwitchCommand() throws Exception {\n AiProviderConfigResponse response = new AiProviderConfigResponse();\n response.setConfigId(2L);\n response.setProviderType(\"OPENAI\");\n response.setModelName(\"gpt-4.1-mini\");\n response.setBaseUrl(\"https://api.openai.com/v1\");\n response.setApiKeyMasked(\"sk-open***mini\");\n when(aiProviderConfigService.activateConfig(eq(\"ou_test_ai_switch\"), eq(2L))).thenReturn(response);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_ai_switch\", \"/ai switch 2\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"OPENAI\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"gpt-4.1-mini\")));\n }\n\n @Test\n void shouldHandleAiConfigCommand() throws Exception {\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(messageEvent(\"ou_test_ai_config\", \"/ai config\")))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"/ai/provider-config-page?openId=ou_test_ai_config\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"API Key\")));\n }\n\n private String messageEvent(String openId, String text) {\n return \"\"\"\n {\n \"header\": {\n \"event_type\": \"im.message.receive_v1\"\n },\n \"event\": {\n \"sender\": {\n \"sender_id\": {\n \"open_id\": \"%s\"\n }\n },\n \"message\": {\n \"message_type\": \"text\",\n \"content\": \"{\\\\\"text\\\\\":\\\\\"%s\\\\\"}\"\n }\n }\n }\n \"\"\".formatted(openId, escapeForMessageContent(text));\n }\n\n private String escapeForMessageContent(String value) {\n return value.replace(\"\\\\\", \"\\\\\\\\\").replace(\"\\\"\", \"\\\\\\\"\");\n }\n}\n"}, "8939f46ec6e004350c3b501b4bd1fd6fc92c2911": {"t": 1785760028.2790637, "n": 8087, "h": "dfc263a06231d430", "c": "package com.verilearn.infra.feishu;\n\nimport com.verilearn.infra.feishu.service.FeishuMessagingService;\nimport com.verilearn.progress.dto.ProgressResponse;\nimport com.verilearn.task.dto.TaskResponse;\nimport com.verilearn.workflow.dto.LearnerSetupResponse;\nimport com.verilearn.workflow.service.LearnerWorkflowService;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.http.MediaType;\nimport org.springframework.test.web.servlet.MockMvc;\n\nimport java.util.List;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.contains;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;\n\n@SpringBootTest\n@AutoConfigureMockMvc\nclass FeishuEventControllerTest {\n\n @Autowired\n private MockMvc mockMvc;\n\n @MockBean\n private LearnerWorkflowService learnerWorkflowService;\n\n @MockBean\n private FeishuMessagingService feishuMessagingService;\n\n @Test\n void shouldReturnChallengeForUrlVerification() throws Exception {\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(\"\"\"\n {\n \"type\": \"url_verification\",\n \"challenge\": \"challenge-token\"\n }\n \"\"\"))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.challenge\").value(\"challenge-token\"));\n }\n\n @Test\n void shouldHandleStartCommand() throws Exception {\n LearnerSetupResponse response = new LearnerSetupResponse();\n response.setTopic(\"Java 后端\");\n response.setDailyMinutes(120);\n response.setInitializedNodeCount(4);\n response.setChapterCount(4);\n when(learnerWorkflowService.setupLearner(any())).thenReturn(response);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(\"\"\"\n {\n \"header\": {\n \"event_type\": \"im.message.receive_v1\"\n },\n \"event\": {\n \"sender\": {\n \"sender_id\": {\n \"open_id\": \"ou_test_start\"\n }\n },\n \"message\": {\n \"message_type\": \"text\",\n \"content\": \"{\\\\\"text\\\\\":\\\\\"/start Java 后端\\\\\"}\"\n }\n }\n }\n \"\"\"))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.code\").value(0))\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/start\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"已为你初始化学习目标:Java 后端\")));\n\n verify(feishuMessagingService).sendTextMessage(eq(\"ou_test_start\"), contains(\"已为你初始化学习目标:Java 后端\"));\n }\n\n @Test\n void shouldHandleTodayCommand() throws Exception {\n TaskResponse taskResponse = new TaskResponse();\n taskResponse.setGoalText(\"学习 Spring Boot fundamentals\");\n taskResponse.setChapterTitle(\"Spring Boot fundamentals\");\n taskResponse.setStepType(\"READ_THEORY\");\n taskResponse.setStatus(\"PENDING\");\n taskResponse.setTheoryFilePath(\"spring-boot/01-spring-boot-fundamentals/user/theory/theory.md\");\n taskResponse.setDemoFilePath(\"spring-boot/01-spring-boot-fundamentals/user/demo/demo-task.md\");\n taskResponse.setValidationItems(List.of());\n when(learnerWorkflowService.generateTodayTask(eq(\"ou_test_today\"))).thenReturn(taskResponse);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(\"\"\"\n {\n \"header\": {\n \"event_type\": \"im.message.receive_v1\"\n },\n \"event\": {\n \"sender\": {\n \"sender_id\": {\n \"open_id\": \"ou_test_today\"\n }\n },\n \"message\": {\n \"message_type\": \"text\",\n \"content\": \"{\\\\\"text\\\\\":\\\\\"/today\\\\\"}\"\n }\n }\n }\n \"\"\"))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/today\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"今日任务:学习 Spring Boot fundamentals\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"theory.md\")))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"demo-task.md\")));\n }\n\n @Test\n void shouldHandleProgressCommand() throws Exception {\n ProgressResponse progressResponse = new ProgressResponse();\n progressResponse.setTopic(\"Java 后端\");\n progressResponse.setTotalNodes(4);\n progressResponse.setInProgressNodes(1);\n progressResponse.setPassedNodes(2);\n progressResponse.setNeedsRetryNodes(1);\n progressResponse.setTotalChapters(4);\n progressResponse.setInProgressChapters(1);\n progressResponse.setCompletedChapters(2);\n progressResponse.setPendingReviewChapters(1);\n when(learnerWorkflowService.getProgress(eq(\"ou_test_progress\"))).thenReturn(progressResponse);\n\n mockMvc.perform(post(\"/api/feishu/events\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(\"\"\"\n {\n \"header\": {\n \"event_type\": \"im.message.receive_v1\"\n },\n \"event\": {\n \"sender\": {\n \"sender_id\": {\n \"open_id\": \"ou_test_progress\"\n }\n },\n \"message\": {\n \"message_type\": \"text\",\n \"content\": \"{\\\\\"text\\\\\":\\\\\"/progress\\\\\"}\"\n }\n }\n }\n \"\"\"))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.data.success\").value(true))\n .andExpect(jsonPath(\"$.data.command\").value(\"/progress\"))\n .andExpect(jsonPath(\"$.data.replyText\").value(Matchers.containsString(\"当前主题:Java 后端\")));\n }\n}\n"}}, "verilearn/src/test/resources/application.yml": {"fa8cbb70ad107c1afd44a6a415eec7b82bc925ce": {"t": 1785746954.6930077, "n": 1545, "h": "1c65b67f3c705402", "c": "spring:\n application:\n name: verilearn\n datasource:\n url: ${VERILEARN_DB_URL:jdbc:mysql://localhost:3306/verilearn?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true}\n username: ${VERILEARN_DB_USERNAME:root}\n password: ${VERILEARN_DB_PASSWORD:root}\n driver-class-name: com.mysql.cj.jdbc.Driver\n sql:\n init:\n mode: always\n schema-locations: classpath:schema-test.sql\n\nserver:\n port: 8080\n\nverilearn:\n ai:\n provider-type: ${VERILEARN_AI_PROVIDER_TYPE:DEEPSEEK}\n base-url: ${VERILEARN_AI_BASE_URL:https://api.deepseek.com}\n api-key: ${VERILEARN_AI_API_KEY:}\n model: ${VERILEARN_AI_MODEL:deepseek-chat}\n security:\n master-key: ${VERILEARN_SECRET_MASTER_KEY:verilearn-local-master-key}\n feishu:\n mode: ${VERILEARN_FEISHU_MODE:LONG_CONNECTION}\n base-url: ${VERILEARN_FEISHU_BASE_URL:https://open.feishu.cn/open-apis}\n public-base-url: ${VERILEARN_FEISHU_PUBLIC_BASE_URL:http://localhost:8080}\n app-id: ${VERILEARN_FEISHU_APP_ID:}\n app-secret: ${VERILEARN_FEISHU_APP_SECRET:}\n verification-token: ${VERILEARN_FEISHU_VERIFICATION_TOKEN:}\n encrypt-key: ${VERILEARN_FEISHU_ENCRYPT_KEY:}\n long-connection-enabled: ${VERILEARN_FEISHU_LONG_CONNECTION_ENABLED:false}\n schedule:\n daily-task-cron: \"0 0 8 * * ?\"\n review-reminder-cron: \"0 0 20 * * ?\"\n storage:\n learning-space-root: ${VERILEARN_LEARNING_SPACE_ROOT:${user.dir}/learning-space}\n\nmybatis-plus:\n configuration:\n map-underscore-to-camel-case: true\n"}, "1b7c9359c834789f65f4778a627c356aaf4d8d2f": {"t": 1785760023.7419295, "n": 1545, "h": "1c65b67f3c705402", "c": "spring:\n application:\n name: verilearn\n datasource:\n url: ${VERILEARN_DB_URL:jdbc:mysql://localhost:3306/verilearn?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true}\n username: ${VERILEARN_DB_USERNAME:root}\n password: ${VERILEARN_DB_PASSWORD:root}\n driver-class-name: com.mysql.cj.jdbc.Driver\n sql:\n init:\n mode: always\n schema-locations: classpath:schema-test.sql\n\nserver:\n port: 8080\n\nverilearn:\n ai:\n provider-type: ${VERILEARN_AI_PROVIDER_TYPE:DEEPSEEK}\n base-url: ${VERILEARN_AI_BASE_URL:https://api.deepseek.com}\n api-key: ${VERILEARN_AI_API_KEY:}\n model: ${VERILEARN_AI_MODEL:deepseek-chat}\n security:\n master-key: ${VERILEARN_SECRET_MASTER_KEY:verilearn-local-master-key}\n feishu:\n mode: ${VERILEARN_FEISHU_MODE:LONG_CONNECTION}\n base-url: ${VERILEARN_FEISHU_BASE_URL:https://open.feishu.cn/open-apis}\n public-base-url: ${VERILEARN_FEISHU_PUBLIC_BASE_URL:http://localhost:8080}\n app-id: ${VERILEARN_FEISHU_APP_ID:}\n app-secret: ${VERILEARN_FEISHU_APP_SECRET:}\n verification-token: ${VERILEARN_FEISHU_VERIFICATION_TOKEN:}\n encrypt-key: ${VERILEARN_FEISHU_ENCRYPT_KEY:}\n long-connection-enabled: ${VERILEARN_FEISHU_LONG_CONNECTION_ENABLED:false}\n schedule:\n daily-task-cron: \"0 0 8 * * ?\"\n review-reminder-cron: \"0 0 20 * * ?\"\n storage:\n learning-space-root: ${VERILEARN_LEARNING_SPACE_ROOT:${user.dir}/learning-space}\n\nmybatis-plus:\n configuration:\n map-underscore-to-camel-case: true\n"}}, "verilearn/src/main/resources/application.yml": {"fa8cbb70ad107c1afd44a6a415eec7b82bc925ce": {"t": 1785746957.313844, "n": 1494, "h": "f0094553e8e6d29b", "c": "spring:\n application:\n name: verilearn\n datasource:\n url: ${VERILEARN_DB_URL:jdbc:mysql://localhost:3306/verilearn?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true}\n username: ${VERILEARN_DB_USERNAME:root}\n password: ${VERILEARN_DB_PASSWORD:root}\n driver-class-name: com.mysql.cj.jdbc.Driver\n sql:\n init:\n mode: never\n\nserver:\n port: 8080\n\nverilearn:\n ai:\n provider-type: ${VERILEARN_AI_PROVIDER_TYPE:DEEPSEEK}\n base-url: ${VERILEARN_AI_BASE_URL:https://api.deepseek.com}\n api-key: ${VERILEARN_AI_API_KEY:}\n model: ${VERILEARN_AI_MODEL:deepseek-chat}\n security:\n master-key: ${VERILEARN_SECRET_MASTER_KEY:verilearn-local-master-key}\n feishu:\n mode: ${VERILEARN_FEISHU_MODE:LONG_CONNECTION}\n base-url: ${VERILEARN_FEISHU_BASE_URL:https://open.feishu.cn/open-apis}\n public-base-url: ${VERILEARN_FEISHU_PUBLIC_BASE_URL:http://localhost:8080}\n app-id: ${VERILEARN_FEISHU_APP_ID:}\n app-secret: ${VERILEARN_FEISHU_APP_SECRET:}\n verification-token: ${VERILEARN_FEISHU_VERIFICATION_TOKEN:}\n encrypt-key: ${VERILEARN_FEISHU_ENCRYPT_KEY:}\n long-connection-enabled: ${VERILEARN_FEISHU_LONG_CONNECTION_ENABLED:false}\n schedule:\n daily-task-cron: \"0 0 8 * * ?\"\n review-reminder-cron: \"0 0 20 * * ?\"\n storage:\n learning-space-root: ${VERILEARN_LEARNING_SPACE_ROOT:${user.dir}/learning-space}\n\nmybatis-plus:\n configuration:\n map-underscore-to-camel-case: true\n"}, "1b7c9359c834789f65f4778a627c356aaf4d8d2f": {"t": 1785760021.7145073, "n": 1494, "h": "f0094553e8e6d29b", "c": "spring:\n application:\n name: verilearn\n datasource:\n url: ${VERILEARN_DB_URL:jdbc:mysql://localhost:3306/verilearn?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true}\n username: ${VERILEARN_DB_USERNAME:root}\n password: ${VERILEARN_DB_PASSWORD:root}\n driver-class-name: com.mysql.cj.jdbc.Driver\n sql:\n init:\n mode: never\n\nserver:\n port: 8080\n\nverilearn:\n ai:\n provider-type: ${VERILEARN_AI_PROVIDER_TYPE:DEEPSEEK}\n base-url: ${VERILEARN_AI_BASE_URL:https://api.deepseek.com}\n api-key: ${VERILEARN_AI_API_KEY:}\n model: ${VERILEARN_AI_MODEL:deepseek-chat}\n security:\n master-key: ${VERILEARN_SECRET_MASTER_KEY:verilearn-local-master-key}\n feishu:\n mode: ${VERILEARN_FEISHU_MODE:LONG_CONNECTION}\n base-url: ${VERILEARN_FEISHU_BASE_URL:https://open.feishu.cn/open-apis}\n public-base-url: ${VERILEARN_FEISHU_PUBLIC_BASE_URL:http://localhost:8080}\n app-id: ${VERILEARN_FEISHU_APP_ID:}\n app-secret: ${VERILEARN_FEISHU_APP_SECRET:}\n verification-token: ${VERILEARN_FEISHU_VERIFICATION_TOKEN:}\n encrypt-key: ${VERILEARN_FEISHU_ENCRYPT_KEY:}\n long-connection-enabled: ${VERILEARN_FEISHU_LONG_CONNECTION_ENABLED:false}\n schedule:\n daily-task-cron: \"0 0 8 * * ?\"\n review-reminder-cron: \"0 0 20 * * ?\"\n storage:\n learning-space-root: ${VERILEARN_LEARNING_SPACE_ROOT:${user.dir}/learning-space}\n\nmybatis-plus:\n configuration:\n map-underscore-to-camel-case: true\n"}, "efef66f528f128dfb15e0d577bfbee0b38e398eb": {"t": 1785760022.9311042, "n": 1495, "h": "c4f106c381f4c7ae", "c": "spring:\n application:\n name: verilearn\n datasource:\n url: ${VERILEARN_DB_URL:jdbc:mysql://localhost:3306/verilearn?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true}\n username: ${VERILEARN_DB_USERNAME:root}\n password: ${VERILEARN_DB_PASSWORD:root}\n driver-class-name: com.mysql.cj.jdbc.Driver\n sql:\n init:\n mode: always\n\nserver:\n port: 8080\n\nverilearn:\n ai:\n provider-type: ${VERILEARN_AI_PROVIDER_TYPE:DEEPSEEK}\n base-url: ${VERILEARN_AI_BASE_URL:https://api.deepseek.com}\n api-key: ${VERILEARN_AI_API_KEY:}\n model: ${VERILEARN_AI_MODEL:deepseek-chat}\n security:\n master-key: ${VERILEARN_SECRET_MASTER_KEY:verilearn-local-master-key}\n feishu:\n mode: ${VERILEARN_FEISHU_MODE:LONG_CONNECTION}\n base-url: ${VERILEARN_FEISHU_BASE_URL:https://open.feishu.cn/open-apis}\n public-base-url: ${VERILEARN_FEISHU_PUBLIC_BASE_URL:http://localhost:8080}\n app-id: ${VERILEARN_FEISHU_APP_ID:}\n app-secret: ${VERILEARN_FEISHU_APP_SECRET:}\n verification-token: ${VERILEARN_FEISHU_VERIFICATION_TOKEN:}\n encrypt-key: ${VERILEARN_FEISHU_ENCRYPT_KEY:}\n long-connection-enabled: ${VERILEARN_FEISHU_LONG_CONNECTION_ENABLED:false}\n schedule:\n daily-task-cron: \"0 0 8 * * ?\"\n review-reminder-cron: \"0 0 20 * * ?\"\n storage:\n learning-space-root: ${VERILEARN_LEARNING_SPACE_ROOT:${user.dir}/learning-space}\n\nmybatis-plus:\n configuration:\n map-underscore-to-camel-case: true\n"}, "372ec20c10f28ac0e283403f65ddf308c3f5856c": {"t": 1785760024.5722015, "n": 1416, "h": "c3ad5bbb26adb968", "c": "spring:\n application:\n name: verilearn\n datasource:\n url: ${VERILEARN_DB_URL:jdbc:mysql://localhost:3306/verilearn?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true}\n username: ${VERILEARN_DB_USERNAME:root}\n password: ${VERILEARN_DB_PASSWORD:root}\n driver-class-name: com.mysql.cj.jdbc.Driver\n sql:\n init:\n mode: always\n\nserver:\n port: 8080\n\nverilearn:\n ai:\n provider-type: ${VERILEARN_AI_PROVIDER_TYPE:DEEPSEEK}\n base-url: ${VERILEARN_AI_BASE_URL:https://api.deepseek.com}\n api-key: ${VERILEARN_AI_API_KEY:}\n model: ${VERILEARN_AI_MODEL:deepseek-chat}\n security:\n master-key: ${VERILEARN_SECRET_MASTER_KEY:verilearn-local-master-key}\n feishu:\n mode: ${VERILEARN_FEISHU_MODE:LONG_CONNECTION}\n base-url: ${VERILEARN_FEISHU_BASE_URL:https://open.feishu.cn/open-apis}\n app-id: ${VERILEARN_FEISHU_APP_ID:}\n app-secret: ${VERILEARN_FEISHU_APP_SECRET:}\n verification-token: ${VERILEARN_FEISHU_VERIFICATION_TOKEN:}\n encrypt-key: ${VERILEARN_FEISHU_ENCRYPT_KEY:}\n long-connection-enabled: ${VERILEARN_FEISHU_LONG_CONNECTION_ENABLED:false}\n schedule:\n daily-task-cron: \"0 0 8 * * ?\"\n review-reminder-cron: \"0 0 20 * * ?\"\n storage:\n learning-space-root: ${VERILEARN_LEARNING_SPACE_ROOT:${user.dir}/learning-space}\n\nmybatis-plus:\n configuration:\n map-underscore-to-camel-case: true\n"}, "8939f46ec6e004350c3b501b4bd1fd6fc92c2911": {"t": 1785760026.237183, "n": 1047, "h": "4c1b1892308a4534", "c": "spring:\n application:\n name: verilearn\n datasource:\n url: ${VERILEARN_DB_URL:jdbc:mysql://localhost:3306/verilearn?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true}\n username: ${VERILEARN_DB_USERNAME:root}\n password: ${VERILEARN_DB_PASSWORD:root}\n driver-class-name: com.mysql.cj.jdbc.Driver\n sql:\n init:\n mode: always\n\nserver:\n port: 8080\n\nverilearn:\n ai:\n base-url: ${VERILEARN_AI_BASE_URL:https://api.deepseek.com}\n api-key: ${VERILEARN_AI_API_KEY:}\n model: ${VERILEARN_AI_MODEL:deepseek-chat}\n feishu:\n base-url: ${VERILEARN_FEISHU_BASE_URL:https://open.feishu.cn/open-apis}\n app-id: ${VERILEARN_FEISHU_APP_ID:}\n app-secret: ${VERILEARN_FEISHU_APP_SECRET:}\n verification-token: ${VERILEARN_FEISHU_VERIFICATION_TOKEN:}\n schedule:\n daily-task-cron: \"0 0 8 * * ?\"\n storage:\n learning-space-root: ${VERILEARN_LEARNING_SPACE_ROOT:${user.dir}/learning-space}\n\nmybatis-plus:\n configuration:\n map-underscore-to-camel-case: true\n"}, "87d75e18e66fdd2575261228b7b86416fac661fa": {"t": 1785760027.4763763, "n": 875, "h": "b092b5cf9f02b075", "c": "spring:\n application:\n name: verilearn\n datasource:\n url: ${VERILEARN_DB_URL:jdbc:mysql://localhost:3306/verilearn?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true}\n username: ${VERILEARN_DB_USERNAME:root}\n password: ${VERILEARN_DB_PASSWORD:root}\n driver-class-name: com.mysql.cj.jdbc.Driver\n sql:\n init:\n mode: always\n\nserver:\n port: 8080\n\nverilearn:\n ai:\n base-url: ${VERILEARN_AI_BASE_URL:https://api.deepseek.com}\n api-key: ${VERILEARN_AI_API_KEY:}\n model: ${VERILEARN_AI_MODEL:deepseek-chat}\n feishu:\n app-id: ${VERILEARN_FEISHU_APP_ID:}\n app-secret: ${VERILEARN_FEISHU_APP_SECRET:}\n verification-token: ${VERILEARN_FEISHU_VERIFICATION_TOKEN:}\n schedule:\n daily-task-cron: \"0 0 8 * * ?\"\n\nmybatis-plus:\n configuration:\n map-underscore-to-camel-case: true\n"}, "df4bf11c97da2f741b34a21844b739fa06844eb8": {"t": 1785760028.6719773, "n": 565, "h": "f0f4d8e33b771b0e", "c": "spring:\n application:\n name: verilearn\n datasource:\n url: jdbc:mysql://localhost:3306/verilearn?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false\n username: root\n password: root\n driver-class-name: com.mysql.cj.jdbc.Driver\n sql:\n init:\n mode: always\n\nserver:\n port: 8080\n\nverilearn:\n ai:\n base-url:\n api-key:\n model:\n feishu:\n app-id:\n app-secret:\n verification-token:\n schedule:\n daily-task-cron: \"0 0 8 * * ?\"\n\nmybatis-plus:\n configuration:\n map-underscore-to-camel-case: true\n"}}}}