test: cover batch schedule service rules
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
|||||||
"cs-agent/internal/models"
|
"cs-agent/internal/models"
|
||||||
"cs-agent/internal/pkg/dto"
|
"cs-agent/internal/pkg/dto"
|
||||||
"cs-agent/internal/pkg/dto/request"
|
"cs-agent/internal/pkg/dto/request"
|
||||||
|
"cs-agent/internal/pkg/dto/response"
|
||||||
"cs-agent/internal/pkg/enums"
|
"cs-agent/internal/pkg/enums"
|
||||||
"cs-agent/internal/services"
|
"cs-agent/internal/services"
|
||||||
|
|
||||||
@@ -175,6 +176,274 @@ func TestAgentTeamScheduleServiceUpdateRejectsHistoricalScheduleByDay(t *testing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAgentTeamScheduleServiceBatchPreviewExpandsSharedRule(t *testing.T) {
|
||||||
|
setupAgentTeamScheduleTestDB(t)
|
||||||
|
createAgentTeamScheduleTestTeams(t, sqls.DB())
|
||||||
|
nextMonday := nextTestWeekday(time.Monday)
|
||||||
|
nextWednesday := nextMonday.AddDate(0, 0, 2)
|
||||||
|
|
||||||
|
preview, err := services.AgentTeamScheduleService.BatchPreview(request.AgentTeamScheduleBatchRequest{
|
||||||
|
TeamIDs: []int64{1, 2},
|
||||||
|
StartDate: nextMonday.Format(time.DateOnly),
|
||||||
|
EndDate: nextMonday.AddDate(0, 0, 6).Format(time.DateOnly),
|
||||||
|
Weekdays: []int{1, 3},
|
||||||
|
StartTime: "09:00",
|
||||||
|
EndTime: "18:00",
|
||||||
|
Remark: "工作日白班",
|
||||||
|
}, testOperator())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("BatchPreview() error = %v", err)
|
||||||
|
}
|
||||||
|
if preview.Total != 4 || len(preview.Items) != 4 {
|
||||||
|
t.Fatalf("expected 4 preview items, got total=%d len=%d", preview.Total, len(preview.Items))
|
||||||
|
}
|
||||||
|
if preview.Conflict {
|
||||||
|
t.Fatalf("expected no conflict, got %+v", preview.Items)
|
||||||
|
}
|
||||||
|
teamNames := make(map[int64]string)
|
||||||
|
for _, item := range preview.Items {
|
||||||
|
if item.TeamName == "" {
|
||||||
|
t.Fatalf("expected all preview items to have team names: %+v", preview.Items)
|
||||||
|
}
|
||||||
|
teamNames[item.TeamID] = item.TeamName
|
||||||
|
}
|
||||||
|
if teamNames[1] == "" || teamNames[2] == "" {
|
||||||
|
t.Fatalf("expected team ids 1 and 2 with names, got %v", teamNames)
|
||||||
|
}
|
||||||
|
type previewKey struct {
|
||||||
|
teamID int64
|
||||||
|
date string
|
||||||
|
}
|
||||||
|
itemsByKey := make(map[previewKey]response.AgentTeamScheduleBatchPreviewItem)
|
||||||
|
for _, item := range preview.Items {
|
||||||
|
itemsByKey[previewKey{teamID: item.TeamID, date: item.Date}] = item
|
||||||
|
}
|
||||||
|
expected := []struct {
|
||||||
|
teamID int64
|
||||||
|
date time.Time
|
||||||
|
weekday int
|
||||||
|
}{
|
||||||
|
{teamID: 1, date: nextMonday, weekday: 1},
|
||||||
|
{teamID: 1, date: nextWednesday, weekday: 3},
|
||||||
|
{teamID: 2, date: nextMonday, weekday: 1},
|
||||||
|
{teamID: 2, date: nextWednesday, weekday: 3},
|
||||||
|
}
|
||||||
|
for _, want := range expected {
|
||||||
|
wantDate := want.date.Format(time.DateOnly)
|
||||||
|
item, ok := itemsByKey[previewKey{teamID: want.teamID, date: wantDate}]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected preview item for teamID=%d date=%s, got %+v", want.teamID, wantDate, preview.Items)
|
||||||
|
}
|
||||||
|
if item.Weekday != want.weekday ||
|
||||||
|
item.StartAt != formatTestDateTime(want.date, "09:00:00") ||
|
||||||
|
item.EndAt != formatTestDateTime(want.date, "18:00:00") ||
|
||||||
|
item.Remark != "工作日白班" {
|
||||||
|
t.Fatalf("unexpected preview item for teamID=%d date=%s: %+v", want.teamID, wantDate, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAgentTeamScheduleServiceBatchPreviewRejectsHistoricalDate(t *testing.T) {
|
||||||
|
setupAgentTeamScheduleTestDB(t)
|
||||||
|
createAgentTeamScheduleTestTeams(t, sqls.DB())
|
||||||
|
yesterday := time.Now().AddDate(0, 0, -1)
|
||||||
|
|
||||||
|
_, err := services.AgentTeamScheduleService.BatchPreview(request.AgentTeamScheduleBatchRequest{
|
||||||
|
TeamIDs: []int64{1},
|
||||||
|
StartDate: yesterday.Format(time.DateOnly),
|
||||||
|
EndDate: yesterday.Format(time.DateOnly),
|
||||||
|
Weekdays: []int{weekdayForRequest(yesterday)},
|
||||||
|
StartTime: "09:00",
|
||||||
|
EndTime: "18:00",
|
||||||
|
}, testOperator())
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected historical batch preview to fail")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "历史日期") {
|
||||||
|
t.Fatalf("expected historical date error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAgentTeamScheduleServiceBatchPreviewRejectsInvalidTimeRange(t *testing.T) {
|
||||||
|
setupAgentTeamScheduleTestDB(t)
|
||||||
|
createAgentTeamScheduleTestTeams(t, sqls.DB())
|
||||||
|
tomorrow := time.Now().AddDate(0, 0, 1)
|
||||||
|
|
||||||
|
_, err := services.AgentTeamScheduleService.BatchPreview(request.AgentTeamScheduleBatchRequest{
|
||||||
|
TeamIDs: []int64{1},
|
||||||
|
StartDate: tomorrow.Format(time.DateOnly),
|
||||||
|
EndDate: tomorrow.Format(time.DateOnly),
|
||||||
|
Weekdays: []int{weekdayForRequest(tomorrow)},
|
||||||
|
StartTime: "18:00",
|
||||||
|
EndTime: "09:00",
|
||||||
|
}, testOperator())
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected invalid time range to fail")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "结束时间必须晚于开始时间") {
|
||||||
|
t.Fatalf("expected invalid time range error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAgentTeamScheduleServiceBatchPreviewRejectsOverLimit(t *testing.T) {
|
||||||
|
setupAgentTeamScheduleTestDB(t)
|
||||||
|
createAgentTeamScheduleTestTeams(t, sqls.DB())
|
||||||
|
today := time.Now()
|
||||||
|
|
||||||
|
_, err := services.AgentTeamScheduleService.BatchPreview(request.AgentTeamScheduleBatchRequest{
|
||||||
|
TeamIDs: []int64{1, 2},
|
||||||
|
StartDate: today.Format(time.DateOnly),
|
||||||
|
EndDate: today.AddDate(0, 0, 260).Format(time.DateOnly),
|
||||||
|
Weekdays: []int{1, 2, 3, 4, 5, 6, 7},
|
||||||
|
StartTime: "09:00",
|
||||||
|
EndTime: "18:00",
|
||||||
|
}, testOperator())
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected over-limit preview to fail")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "500") {
|
||||||
|
t.Fatalf("expected 500 limit error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAgentTeamScheduleServiceBatchPreviewMarksConflicts(t *testing.T) {
|
||||||
|
db := setupAgentTeamScheduleTestDB(t)
|
||||||
|
createAgentTeamScheduleTestTeams(t, db)
|
||||||
|
targetDay := time.Now().AddDate(0, 0, 2)
|
||||||
|
existing := models.AgentTeamSchedule{
|
||||||
|
TeamID: 1,
|
||||||
|
StartAt: parseTestDateTime(t, formatTestDateTime(targetDay, "10:00:00")),
|
||||||
|
EndAt: parseTestDateTime(t, formatTestDateTime(targetDay, "12:00:00")),
|
||||||
|
Status: enums.StatusOk,
|
||||||
|
}
|
||||||
|
if err := db.Create(&existing).Error; err != nil {
|
||||||
|
t.Fatalf("create existing schedule error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
preview, err := services.AgentTeamScheduleService.BatchPreview(request.AgentTeamScheduleBatchRequest{
|
||||||
|
TeamIDs: []int64{1, 2},
|
||||||
|
StartDate: targetDay.Format(time.DateOnly),
|
||||||
|
EndDate: targetDay.Format(time.DateOnly),
|
||||||
|
Weekdays: []int{weekdayForRequest(targetDay)},
|
||||||
|
StartTime: "09:00",
|
||||||
|
EndTime: "18:00",
|
||||||
|
}, testOperator())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("BatchPreview() error = %v", err)
|
||||||
|
}
|
||||||
|
if !preview.Conflict {
|
||||||
|
t.Fatalf("expected preview conflict, got %+v", preview)
|
||||||
|
}
|
||||||
|
itemsByTeamID := make(map[int64]response.AgentTeamScheduleBatchPreviewItem)
|
||||||
|
for _, item := range preview.Items {
|
||||||
|
itemsByTeamID[item.TeamID] = item
|
||||||
|
}
|
||||||
|
team1Item, ok := itemsByTeamID[1]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected team 1 preview item, got %+v", preview.Items)
|
||||||
|
}
|
||||||
|
team2Item, ok := itemsByTeamID[2]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected team 2 preview item, got %+v", preview.Items)
|
||||||
|
}
|
||||||
|
if !team1Item.Conflict || team1Item.ConflictReason == "" {
|
||||||
|
t.Fatalf("expected team 1 preview item to be marked as conflict: %+v", team1Item)
|
||||||
|
}
|
||||||
|
if team2Item.Conflict {
|
||||||
|
t.Fatalf("expected team 2 preview item to have no conflict: %+v", team2Item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAgentTeamScheduleServiceBatchGenerateCreatesAllSchedules(t *testing.T) {
|
||||||
|
db := setupAgentTeamScheduleTestDB(t)
|
||||||
|
createAgentTeamScheduleTestTeams(t, db)
|
||||||
|
nextMonday := nextTestWeekday(time.Monday)
|
||||||
|
|
||||||
|
result, err := services.AgentTeamScheduleService.BatchGenerate(request.AgentTeamScheduleBatchRequest{
|
||||||
|
TeamIDs: []int64{1, 2},
|
||||||
|
StartDate: nextMonday.Format(time.DateOnly),
|
||||||
|
EndDate: nextMonday.AddDate(0, 0, 2).Format(time.DateOnly),
|
||||||
|
Weekdays: []int{1, 3},
|
||||||
|
StartTime: "09:00",
|
||||||
|
EndTime: "18:00",
|
||||||
|
Remark: "批量生成",
|
||||||
|
}, testOperator())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("BatchGenerate() error = %v", err)
|
||||||
|
}
|
||||||
|
if result.Created != 4 {
|
||||||
|
t.Fatalf("expected 4 created schedules, got %d", result.Created)
|
||||||
|
}
|
||||||
|
var schedules []models.AgentTeamSchedule
|
||||||
|
if err := db.Where("remark = ?", "批量生成").
|
||||||
|
Order("team_id ASC, start_at ASC").
|
||||||
|
Find(&schedules).Error; err != nil {
|
||||||
|
t.Fatalf("query generated schedules error = %v", err)
|
||||||
|
}
|
||||||
|
if len(schedules) != 4 {
|
||||||
|
t.Fatalf("expected 4 stored schedules, got %d: %+v", len(schedules), schedules)
|
||||||
|
}
|
||||||
|
expected := []struct {
|
||||||
|
teamID int64
|
||||||
|
startAt string
|
||||||
|
endAt string
|
||||||
|
}{
|
||||||
|
{teamID: 1, startAt: formatTestDateTime(nextMonday, "09:00:00"), endAt: formatTestDateTime(nextMonday, "18:00:00")},
|
||||||
|
{teamID: 1, startAt: formatTestDateTime(nextMonday.AddDate(0, 0, 2), "09:00:00"), endAt: formatTestDateTime(nextMonday.AddDate(0, 0, 2), "18:00:00")},
|
||||||
|
{teamID: 2, startAt: formatTestDateTime(nextMonday, "09:00:00"), endAt: formatTestDateTime(nextMonday, "18:00:00")},
|
||||||
|
{teamID: 2, startAt: formatTestDateTime(nextMonday.AddDate(0, 0, 2), "09:00:00"), endAt: formatTestDateTime(nextMonday.AddDate(0, 0, 2), "18:00:00")},
|
||||||
|
}
|
||||||
|
for i, want := range expected {
|
||||||
|
got := schedules[i]
|
||||||
|
if got.TeamID != want.teamID ||
|
||||||
|
got.StartAt.Format(time.DateTime) != want.startAt ||
|
||||||
|
got.EndAt.Format(time.DateTime) != want.endAt {
|
||||||
|
t.Fatalf("unexpected schedule at index %d: got teamID=%d startAt=%s endAt=%s, want teamID=%d startAt=%s endAt=%s",
|
||||||
|
i,
|
||||||
|
got.TeamID,
|
||||||
|
got.StartAt.Format(time.DateTime),
|
||||||
|
got.EndAt.Format(time.DateTime),
|
||||||
|
want.teamID,
|
||||||
|
want.startAt,
|
||||||
|
want.endAt,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAgentTeamScheduleServiceBatchGenerateRejectsConflictsWithoutPartialCreate(t *testing.T) {
|
||||||
|
db := setupAgentTeamScheduleTestDB(t)
|
||||||
|
createAgentTeamScheduleTestTeams(t, db)
|
||||||
|
targetDay := time.Now().AddDate(0, 0, 2)
|
||||||
|
existing := models.AgentTeamSchedule{
|
||||||
|
TeamID: 1,
|
||||||
|
StartAt: parseTestDateTime(t, formatTestDateTime(targetDay, "10:00:00")),
|
||||||
|
EndAt: parseTestDateTime(t, formatTestDateTime(targetDay, "12:00:00")),
|
||||||
|
Status: enums.StatusOk,
|
||||||
|
}
|
||||||
|
if err := db.Create(&existing).Error; err != nil {
|
||||||
|
t.Fatalf("create existing schedule error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := services.AgentTeamScheduleService.BatchGenerate(request.AgentTeamScheduleBatchRequest{
|
||||||
|
TeamIDs: []int64{1, 2},
|
||||||
|
StartDate: targetDay.Format(time.DateOnly),
|
||||||
|
EndDate: targetDay.Format(time.DateOnly),
|
||||||
|
Weekdays: []int{weekdayForRequest(targetDay)},
|
||||||
|
StartTime: "09:00",
|
||||||
|
EndTime: "18:00",
|
||||||
|
Remark: "不应创建",
|
||||||
|
}, testOperator())
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected conflict batch generate to fail")
|
||||||
|
}
|
||||||
|
var count int64
|
||||||
|
db.Model(&models.AgentTeamSchedule{}).Where("remark = ?", "不应创建").Count(&count)
|
||||||
|
if count != 0 {
|
||||||
|
t.Fatalf("expected no partial creates, got %d", count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func setupAgentTeamScheduleTestDB(t *testing.T) *gorm.DB {
|
func setupAgentTeamScheduleTestDB(t *testing.T) *gorm.DB {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
@@ -241,6 +510,26 @@ func formatTestDateTime(date time.Time, clock string) string {
|
|||||||
return date.Format(time.DateOnly) + " " + clock
|
return date.Format(time.DateOnly) + " " + clock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nextTestWeekday(target time.Weekday) time.Time {
|
||||||
|
ret := startOfTestDay(time.Now()).AddDate(0, 0, 1)
|
||||||
|
for ret.Weekday() != target {
|
||||||
|
ret = ret.AddDate(0, 0, 1)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func startOfTestDay(value time.Time) time.Time {
|
||||||
|
year, month, day := value.In(time.Local).Date()
|
||||||
|
return time.Date(year, month, day, 0, 0, 0, 0, time.Local)
|
||||||
|
}
|
||||||
|
|
||||||
|
func weekdayForRequest(value time.Time) int {
|
||||||
|
if value.Weekday() == time.Sunday {
|
||||||
|
return 7
|
||||||
|
}
|
||||||
|
return int(value.Weekday())
|
||||||
|
}
|
||||||
|
|
||||||
func createFutureAgentTeamSchedule(t *testing.T, db *gorm.DB) int64 {
|
func createFutureAgentTeamSchedule(t *testing.T, db *gorm.DB) int64 {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
tomorrow := time.Now().AddDate(0, 0, 1)
|
tomorrow := time.Now().AddDate(0, 0, 1)
|
||||||
|
|||||||
Reference in New Issue
Block a user