package logic import ( "testing" "tool-api/internal/consts" ) // TestPlanSeatNumbers 覆盖判据②:新增桌自动生成 1..N 座。 func TestPlanSeatNumbers(t *testing.T) { got := planSeatNumbers(5) want := []int{1, 2, 3, 4, 5} if len(got) != len(want) { t.Fatalf("planSeatNumbers(5) len=%d want %d", len(got), len(want)) } for i := range want { if got[i] != want[i] { t.Fatalf("planSeatNumbers(5)=%v want %v", got, want) } } if n := len(planSeatNumbers(0)); n != 0 { t.Fatalf("planSeatNumbers(0) len=%d want 0", n) } if n := len(planSeatNumbers(-3)); n != 0 { t.Fatalf("planSeatNumbers(-3) len=%d want 0", n) } } // fullSeats 构造 1..n 的座位快照,空位。 func fullSeats(n int) []seatBrief { out := make([]seatBrief, 0, n) for i := 1; i <= n; i++ { out = append(out, seatBrief{Id: int64(i), TableId: 1, SeatNo: i}) } return out } // TestPlanCapacityChangeIncrease 覆盖判据③(增大):10→12 补出 11、12 空位。 func TestPlanCapacityChangeIncrease(t *testing.T) { toAdd, toRemove := planCapacityChange(fullSeats(10), 12) if len(toRemove) != 0 { t.Fatalf("增大容量不应移除座位,got %v", toRemove) } if len(toAdd) != 2 || toAdd[0] != 11 || toAdd[1] != 12 { t.Fatalf("toAdd=%v want [11 12]", toAdd) } } // TestPlanCapacityChangeDecrease 覆盖判据③(减小):10→8 移除 9、10 号,且能识别其上员工(移入未分配)。 func TestPlanCapacityChangeDecrease(t *testing.T) { seats := fullSeats(10) // 9、10 号位坐着员工 seats[8].EmployeeId = 201 seats[9].EmployeeId = 202 toAdd, toRemove := planCapacityChange(seats, 8) if len(toAdd) != 0 { t.Fatalf("减小容量不应补座,got %v", toAdd) } if len(toRemove) != 2 { t.Fatalf("toRemove len=%d want 2(9、10 号)", len(toRemove)) } moved := 0 removedNos := map[int]bool{} for _, s := range toRemove { removedNos[s.SeatNo] = true if s.EmployeeId != 0 { moved++ } } if !removedNos[9] || !removedNos[10] { t.Fatalf("应移除 9、10 号,got %v", removedNos) } if moved != 2 { t.Fatalf("应识别 2 名员工将移入未分配,got %d", moved) } } // TestPlanAutoAssignFillPreservesManual 覆盖判据⑤:fill 跳过并保留手工座位,不动其编号。 func TestPlanAutoAssignFillPreservesManual(t *testing.T) { seats := []seatBrief{ {Id: 1, TableId: 1, SeatNo: 1, IsManual: false, EmployeeId: 0}, {Id: 2, TableId: 1, SeatNo: 2, IsManual: true, EmployeeId: 0}, // 手工空位:fill 不应占用 {Id: 3, TableId: 1, SeatNo: 3, IsManual: false, EmployeeId: 0}, } assigns, remaining, overridden := planAutoAssign(seats, []int64{101, 102, 103}, consts.SeatModeFill) if overridden != 0 { t.Fatalf("fill 不应覆盖手工标记,got %d", overridden) } // 只有 2 个非手工空位,故只分配 2 人,剩 1 人 if len(assigns) != 2 { t.Fatalf("fill 应分配 2 人,got %d (%v)", len(assigns), assigns) } for _, a := range assigns { if a.SeatId == 2 { t.Fatalf("fill 不应占用手工座位 id=2,got %v", assigns) } } if len(remaining) != 1 || remaining[0] != 103 { t.Fatalf("remaining=%v want [103]", remaining) } } // TestPlanAutoAssignReorderOverridesManual 覆盖判据⑤:reorder 覆盖手工号并清空手工标记。 func TestPlanAutoAssignReorderOverridesManual(t *testing.T) { seats := []seatBrief{ {Id: 1, TableId: 1, SeatNo: 1, IsManual: false, EmployeeId: 0}, {Id: 2, TableId: 1, SeatNo: 2, IsManual: true, EmployeeId: 0}, // 手工位,reorder 可用 {Id: 3, TableId: 1, SeatNo: 3, IsManual: true, EmployeeId: 9}, // 手工位且已坐人 } assigns, remaining, overridden := planAutoAssign(seats, []int64{101, 102}, consts.SeatModeReorder) if overridden != 2 { t.Fatalf("reorder 应清空 2 个手工标记,got %d", overridden) } if len(assigns) != 2 { t.Fatalf("reorder 应分配 2 人(含手工空位),got %d (%v)", len(assigns), assigns) } // 手工空位 id=2 现在可被占用 used := map[int64]bool{} for _, a := range assigns { used[a.SeatId] = true } if !used[2] { t.Fatalf("reorder 应可使用手工空位 id=2,got %v", assigns) } if used[3] { t.Fatalf("已坐人的座位不应被重分配,got %v", assigns) } if len(remaining) != 0 { t.Fatalf("remaining=%v want []", remaining) } } // TestPlanAutoAssignKeepsOccupied 已坐人的座位一律保持不动(fill 与 reorder 皆然)。 func TestPlanAutoAssignKeepsOccupied(t *testing.T) { seats := []seatBrief{ {Id: 1, TableId: 1, SeatNo: 1, EmployeeId: 77}, {Id: 2, TableId: 1, SeatNo: 2, EmployeeId: 0}, } for _, mode := range []string{consts.SeatModeFill, consts.SeatModeReorder} { assigns, _, _ := planAutoAssign(seats, []int64{101}, mode) if len(assigns) != 1 || assigns[0].SeatId != 2 { t.Fatalf("mode=%s 应只填 id=2,got %v", mode, assigns) } } } // TestPlanSeatSwapInvariant 覆盖 Part 1(对调):对调后两座编号互换,且全桌仍为无重复 1..capacity。 func TestPlanSeatSwapInvariant(t *testing.T) { seats := fullSeats(10) // 1..10,Id == SeatNo before := append([]seatBrief(nil), seats...) // 把 3 号座(id=3)改成 8 号 plan, err := planSeatSwap(seats, 3, 8) if err != nil { t.Fatalf("对调不应报错: %v", err) } if plan.SeatBId != 8 || plan.SeatANewNo != 8 || plan.SeatBNewNo != 3 { t.Fatalf("对调计划错误: %+v", plan) } if plan.TempNo <= 10 { t.Fatalf("中转号必须避开现有编号,got %d", plan.TempNo) } // 入参快照不应被修改(保证「目标不存在时数据不变」这一性质的基础) for i := range seats { if seats[i] != before[i] { t.Fatalf("planSeatSwap 不应修改入参快照") } } // 模拟事务三步(B→temp,A→target,B→cur)后的编号集合,断言不变式:无重复 1..10 nos := make([]int, len(seats)) for i, s := range seats { nos[i] = s.SeatNo } apply := func(id int64, no int) { for i := range seats { if seats[i].Id == id { nos[i] = no } } } apply(plan.SeatBId, plan.TempNo) apply(plan.SeatAId, plan.SeatANewNo) apply(plan.SeatBId, plan.SeatBNewNo) seen := map[int]bool{} for i, n := range nos { if n < 1 || n > 10 { t.Fatalf("编号越界: %d(全桌=%v)", n, nos) } if seen[n] { t.Fatalf("编号重复: %d(全桌=%v)", n, nos) } seen[n] = true // A→8、B(原 8 号)→3 if seats[i].Id == plan.SeatAId && n != 8 { t.Fatalf("A 应对调到 8 号,got %d", n) } if seats[i].Id == plan.SeatBId && n != 3 { t.Fatalf("B 应对调到 3 号,got %d", n) } } if len(seen) != 10 { t.Fatalf("全桌应仍为 10 个不同编号,got %v", nos) } // 员工绑定未被搬运:planSeatSwap 不携带 employee 字段,AdminSeatSave 仅在请求改绑定时才写 // employee_id —— 故对调路径下 A 行的 employee_id 保持不变(保证查座显示「8 号·张三」)。 } // TestPlanSeatSwapTargetMissing 目标号不存在/超范围 → 报错;快照不变;同号幂等。 func TestPlanSeatSwapTargetMissing(t *testing.T) { seats := fullSeats(10) if _, err := planSeatSwap(seats, 3, 11); err == nil { t.Fatalf("目标号 11 超出 1..capacity 应报错") } if _, err := planSeatSwap(seats, 3, 0); err == nil { t.Fatalf("目标号 0 非法应报错") } if p, err := planSeatSwap(seats, 3, 3); err != nil || p.SeatBId != 0 { t.Fatalf("目标号等于当前号应幂等(SeatBId=0),got %+v err=%v", p, err) } // 目标号不存在于该桌(换到别的 table 的号)—— 用只含 1..5 的桌模拟 small := fullSeats(5) if _, err := planSeatSwap(small, 3, 8); err == nil { t.Fatalf("该桌没有 8 号座位,应报错") } }