Initial qiwei secondary development handoff
This commit is contained in:
209
helper/after_sales_knowledge_test.go
Normal file
209
helper/after_sales_knowledge_test.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestAfterSalesResolveIssueArchivesKnowledgeCase(t *testing.T) {
|
||||
cleanupAfterSalesKnowledgeTestFiles(t)
|
||||
rebuildCh := stubAfterSalesKnowledgeRebuild(t)
|
||||
|
||||
engine := &AfterSalesIssueEngine{issues: []AfterSalesIssue{{
|
||||
ID: "i1",
|
||||
CreatedAt: "2026-06-04T09:00:00+08:00",
|
||||
Status: afterSalesIssueStatusPending,
|
||||
RoomName: "售后群",
|
||||
CustomerName: "华南客户",
|
||||
IssueContent: "镜头无法调焦",
|
||||
AISuggestion: "检查调焦机构",
|
||||
AssignedEngineerID: "engineer-a",
|
||||
ImagePaths: []string{"a.jpg"},
|
||||
}}}
|
||||
|
||||
knowledgeCase, err := engine.resolveIssue("i1", "已确认调焦环松动,重新固定后恢复。")
|
||||
if err != nil {
|
||||
t.Fatalf("resolve issue: %v", err)
|
||||
}
|
||||
if engine.issues[0].Status != afterSalesIssueStatusResolved {
|
||||
t.Fatalf("expected resolved issue, got %#v", engine.issues[0])
|
||||
}
|
||||
if engine.issues[0].KnowledgeSourcePath == "" || engine.issues[0].KnowledgeArchivedAt == "" {
|
||||
t.Fatalf("expected issue knowledge metadata, got %#v", engine.issues[0])
|
||||
}
|
||||
if knowledgeCase.IssueID != "i1" || knowledgeCase.ImageCount != 1 {
|
||||
t.Fatalf("unexpected case: %#v", knowledgeCase)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(knowledgeCase.MarkdownPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read markdown: %v", err)
|
||||
}
|
||||
text := string(data)
|
||||
for _, want := range []string{"问题ID:i1", "镜头无法调焦", "已确认调焦环松动", "检查调焦机构", "售后群", "华南客户"} {
|
||||
if !strings.Contains(text, want) {
|
||||
t.Fatalf("expected markdown to contain %q, got %s", want, text)
|
||||
}
|
||||
}
|
||||
cases, err := listAfterSalesKnowledgeCases()
|
||||
if err != nil {
|
||||
t.Fatalf("list cases: %v", err)
|
||||
}
|
||||
if len(cases) != 1 || cases[0].IssueID != "i1" {
|
||||
t.Fatalf("expected one listed case, got %#v", cases)
|
||||
}
|
||||
select {
|
||||
case <-rebuildCh:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("expected knowledge rebuild hook")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAfterSalesResolveIssueRequiresResolution(t *testing.T) {
|
||||
cleanupAfterSalesKnowledgeTestFiles(t)
|
||||
rebuildCh := stubAfterSalesKnowledgeRebuild(t)
|
||||
engine := &AfterSalesIssueEngine{issues: []AfterSalesIssue{{
|
||||
ID: "i1",
|
||||
Status: afterSalesIssueStatusPending,
|
||||
RoomName: "售后群",
|
||||
}}}
|
||||
|
||||
if _, err := engine.resolveIssue("i1", " "); err == nil {
|
||||
t.Fatal("expected empty resolution error")
|
||||
}
|
||||
if engine.issues[0].Status != afterSalesIssueStatusPending {
|
||||
t.Fatalf("expected status unchanged, got %#v", engine.issues[0])
|
||||
}
|
||||
select {
|
||||
case <-rebuildCh:
|
||||
t.Fatal("did not expect rebuild")
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func TestAfterSalesKnowledgeCaseOverwrite(t *testing.T) {
|
||||
cleanupAfterSalesKnowledgeTestFiles(t)
|
||||
stubAfterSalesKnowledgeRebuild(t)
|
||||
engine := &AfterSalesIssueEngine{issues: []AfterSalesIssue{{
|
||||
ID: "i1",
|
||||
CreatedAt: "2026-06-04T09:00:00+08:00",
|
||||
Status: afterSalesIssueStatusPending,
|
||||
IssueContent: "图像模糊",
|
||||
AISuggestion: "检查焦距",
|
||||
}}}
|
||||
|
||||
first, err := engine.resolveIssue("i1", "第一次处理方案")
|
||||
if err != nil {
|
||||
t.Fatalf("first resolve: %v", err)
|
||||
}
|
||||
second, err := engine.resolveIssue("i1", "第二次处理方案")
|
||||
if err != nil {
|
||||
t.Fatalf("second resolve: %v", err)
|
||||
}
|
||||
if first.MarkdownPath != second.MarkdownPath {
|
||||
t.Fatalf("expected same markdown path, got %s and %s", first.MarkdownPath, second.MarkdownPath)
|
||||
}
|
||||
cases, err := listAfterSalesKnowledgeCases()
|
||||
if err != nil {
|
||||
t.Fatalf("list cases: %v", err)
|
||||
}
|
||||
if len(cases) != 1 || cases[0].ResolutionContent != "第二次处理方案" {
|
||||
t.Fatalf("expected overwritten single case, got %#v", cases)
|
||||
}
|
||||
data, err := os.ReadFile(second.MarkdownPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read markdown: %v", err)
|
||||
}
|
||||
if strings.Contains(string(data), "第一次处理方案") || !strings.Contains(string(data), "第二次处理方案") {
|
||||
t.Fatalf("expected markdown overwrite, got %s", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAfterSalesKnowledgeSyncLegacyResolvedIssue(t *testing.T) {
|
||||
cleanupAfterSalesKnowledgeTestFiles(t)
|
||||
rebuildCh := stubAfterSalesKnowledgeRebuild(t)
|
||||
engine := &AfterSalesIssueEngine{issues: []AfterSalesIssue{{
|
||||
ID: "legacy",
|
||||
CreatedAt: "2026-06-02T11:10:24+08:00",
|
||||
UpdatedAt: "2026-06-04T11:03:34+08:00",
|
||||
Status: afterSalesIssueStatusResolved,
|
||||
RoomName: "刘羽、JM.、C",
|
||||
CustomerName: "JM.",
|
||||
IssueContent: "客户询问客服身份",
|
||||
AISuggestion: "客户询问客服身份,需确认是否需要进一步解释或提供帮助",
|
||||
AssignedEngineerID: "1688855899845302",
|
||||
NotifyStatus: afterSalesNotifySent,
|
||||
}}}
|
||||
|
||||
if err := engine.syncResolvedKnowledgeCases(); err != nil {
|
||||
t.Fatalf("sync legacy resolved: %v", err)
|
||||
}
|
||||
cases, err := listAfterSalesKnowledgeCases()
|
||||
if err != nil {
|
||||
t.Fatalf("list cases: %v", err)
|
||||
}
|
||||
if len(cases) != 1 || cases[0].IssueID != "legacy" {
|
||||
t.Fatalf("expected one synced legacy case, got %#v", cases)
|
||||
}
|
||||
if cases[0].ResolutionContent != "客户询问客服身份,需确认是否需要进一步解释或提供帮助" {
|
||||
t.Fatalf("expected AI suggestion fallback, got %#v", cases[0])
|
||||
}
|
||||
if engine.issues[0].KnowledgeSourcePath == "" || engine.issues[0].ResolutionContent == "" {
|
||||
t.Fatalf("expected issue backfill metadata, got %#v", engine.issues[0])
|
||||
}
|
||||
if !fileExists(cases[0].MarkdownPath) {
|
||||
t.Fatalf("expected markdown file at %s", cases[0].MarkdownPath)
|
||||
}
|
||||
select {
|
||||
case <-rebuildCh:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("expected knowledge rebuild hook")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAfterSalesKnowledgeCaseMissingMarkdownFlag(t *testing.T) {
|
||||
cleanupAfterSalesKnowledgeTestFiles(t)
|
||||
stubAfterSalesKnowledgeRebuild(t)
|
||||
engine := &AfterSalesIssueEngine{issues: []AfterSalesIssue{{ID: "i1", Status: afterSalesIssueStatusPending, IssueContent: "报错"}}}
|
||||
knowledgeCase, err := engine.resolveIssue("i1", "重启设备恢复")
|
||||
if err != nil {
|
||||
t.Fatalf("resolve issue: %v", err)
|
||||
}
|
||||
if err := os.Remove(knowledgeCase.MarkdownPath); err != nil {
|
||||
t.Fatalf("remove markdown: %v", err)
|
||||
}
|
||||
cases, err := listAfterSalesKnowledgeCases()
|
||||
if err != nil {
|
||||
t.Fatalf("list cases: %v", err)
|
||||
}
|
||||
if len(cases) != 1 || !cases[0].MissingMarkdown {
|
||||
t.Fatalf("expected missing markdown flag, got %#v", cases)
|
||||
}
|
||||
}
|
||||
|
||||
func stubAfterSalesKnowledgeRebuild(t *testing.T) chan struct{} {
|
||||
t.Helper()
|
||||
oldHook := afterSalesKnowledgeRebuildHook
|
||||
ch := make(chan struct{}, 8)
|
||||
afterSalesKnowledgeRebuildHook = func() {
|
||||
ch <- struct{}{}
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
afterSalesKnowledgeRebuildHook = oldHook
|
||||
})
|
||||
return ch
|
||||
}
|
||||
|
||||
func cleanupAfterSalesKnowledgeTestFiles(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Cleanup(func() {
|
||||
_ = os.Remove(afterSalesKnowledgeCasesPath())
|
||||
_ = os.Remove(afterSalesIssuesPath())
|
||||
_ = os.RemoveAll(afterSalesKnowledgeMarkdownDir())
|
||||
})
|
||||
_ = os.Remove(afterSalesKnowledgeCasesPath())
|
||||
_ = os.Remove(afterSalesIssuesPath())
|
||||
_ = os.RemoveAll(afterSalesKnowledgeMarkdownDir())
|
||||
}
|
||||
Reference in New Issue
Block a user