package service import ( "testing" "time" "kefu-cloud/server/internal/model" ) func TestNormalizeWelcomeSegments(t *testing.T) { SetImagePublicBase("http://localhost:9100/kefu") _, _, err := NormalizeWelcomeSegments(nil) if err == nil { t.Fatal("空列表应失败") } ok, first, err := NormalizeWelcomeSegments([]WelcomeSegment{ {Type: "text", Content: " 你好 "}, {Type: "image", Content: "http://localhost:9100/kefu/tenants/1/welcome/a.webp"}, {Type: "text", Content: "请描述问题"}, }) if err != nil { t.Fatalf("合法多段应通过: %v", err) } if len(ok) != 3 || ok[0].Content != "你好" || first != "你好" { t.Fatalf("规范化结果异常: %+v first=%s", ok, first) } _, _, err = NormalizeWelcomeSegments([]WelcomeSegment{ {Type: "image", Content: "https://evil.example/x.png"}, }) if err == nil { t.Fatal("非白名单图片 URL 应失败") } tooMany := make([]WelcomeSegment, MaxWelcomeSegments+1) for i := range tooMany { tooMany[i] = WelcomeSegment{Type: "text", Content: "x"} } if _, _, err := NormalizeWelcomeSegments(tooMany); err == nil { t.Fatal("超过段数上限应失败") } } func TestResolveWelcomeSegmentsCompat(t *testing.T) { // 仅有旧字段 s1 := &model.TenantSetting{WelcomeMessage: "旧欢迎语"} segs := ResolveWelcomeSegments(s1) if len(segs) != 1 || segs[0].Type != "text" || segs[0].Content != "旧欢迎语" { t.Fatalf("旧字段兼容失败: %+v", segs) } // 优先 JSON s2 := &model.TenantSetting{ WelcomeMessage: "旧", WelcomeMessagesJSON: `[{"type":"text","content":"新1"},{"type":"image","content":"http://x/a.webp"}]`, } segs = ResolveWelcomeSegments(s2) if len(segs) != 2 || segs[0].Content != "新1" { t.Fatalf("JSON 优先失败: %+v", segs) } // 空 → 默认 segs = ResolveWelcomeSegments(&model.TenantSetting{}) if len(segs) != 1 || segs[0].Content != DefaultWelcomeMessage { t.Fatalf("默认欢迎语失败: %+v", segs) } } func TestNormalizeWorkHourSlot(t *testing.T) { cases := []struct { in string want string wantErr bool }{ {"", "", false}, {" 休息 ", "", false}, {"全天", "全天", false}, {"09:00-18:00", "09:00-18:00", false}, {" 9:30 - 18:05 ", "09:30-18:05", false}, {"22:00-06:00", "22:00-06:00", false}, {"全天营业", "", true}, {"09:00", "", true}, {"25:00-26:00", "", true}, {"09:00-18:00-extra", "", true}, } for _, tc := range cases { got, err := NormalizeWorkHourSlot(tc.in) if tc.wantErr { if err == nil { t.Fatalf("NormalizeWorkHourSlot(%q) 期望错误,got=%q", tc.in, got) } continue } if err != nil { t.Fatalf("NormalizeWorkHourSlot(%q) 意外错误: %v", tc.in, err) } if got != tc.want { t.Fatalf("NormalizeWorkHourSlot(%q)=%q, want %q", tc.in, got, tc.want) } } } func TestSlotCoversLocal(t *testing.T) { loc, err := time.LoadLocation("Asia/Shanghai") if err != nil { t.Fatal(err) } // 2026-03-16 周一 mk := func(h, m int) time.Time { return time.Date(2026, 3, 16, h, m, 0, 0, loc) } if !slotCoversLocal("全天", mk(3, 0), loc) { t.Fatal("全天应覆盖任意时刻") } if slotCoversLocal("", mk(10, 0), loc) { t.Fatal("空应视为休息") } if slotCoversLocal("休息", mk(10, 0), loc) { t.Fatal("休息应不覆盖") } // 09:00-18:00 半开区间 [start, end) if !slotCoversLocal("09:00-18:00", mk(9, 0), loc) { t.Fatal("09:00 应在工作时间内") } if !slotCoversLocal("09:00-18:00", mk(17, 59), loc) { t.Fatal("17:59 应在工作时间内") } if slotCoversLocal("09:00-18:00", mk(18, 0), loc) { t.Fatal("18:00 不应在工作时间内(半开)") } if slotCoversLocal("09:00-18:00", mk(8, 59), loc) { t.Fatal("08:59 不应在工作时间内") } // 跨午夜 22:00-06:00 if !slotCoversLocal("22:00-06:00", mk(23, 0), loc) { t.Fatal("23:00 应在跨午夜时段内") } if !slotCoversLocal("22:00-06:00", mk(5, 0), loc) { t.Fatal("05:00 应在跨午夜时段内") } if slotCoversLocal("22:00-06:00", mk(12, 0), loc) { t.Fatal("12:00 不应在跨午夜时段内") } } func TestIsWithinWorkHoursCustomRange(t *testing.T) { setting := &model.TenantSetting{ Timezone: "Asia/Shanghai", WorkHoursJSON: `{ "monday":"09:30-12:00", "tuesday":"全天", "wednesday":"", "thursday":"22:00-06:00", "friday":"全天", "saturday":"休息", "sunday":"" }`, } loc, _ := time.LoadLocation("Asia/Shanghai") // 周一 10:00 → 在 09:30-12:00 mon := time.Date(2026, 3, 16, 10, 0, 0, 0, loc) if !IsWithinWorkHours(setting, mon) { t.Fatal("周一 10:00 应在自定义时段内") } // 周一 14:00 → 不在 monPM := time.Date(2026, 3, 16, 14, 0, 0, 0, loc) if IsWithinWorkHours(setting, monPM) { t.Fatal("周一 14:00 不应在工作时间") } // 周三休息 wed := time.Date(2026, 3, 18, 10, 0, 0, 0, loc) if IsWithinWorkHours(setting, wed) { t.Fatal("周三应休息") } // 周四跨午夜 23:00 thu := time.Date(2026, 3, 19, 23, 0, 0, 0, loc) if !IsWithinWorkHours(setting, thu) { t.Fatal("周四 23:00 应在跨午夜时段") } }