230 lines
7.5 KiB
Go
230 lines
7.5 KiB
Go
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 号座位,应报错")
|
||
}
|
||
}
|