Files
qitongxue-api/internal/logic/event_seat_test.go
2026-09-29 10:57:01 +08:00

230 lines
7.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 号座位,应报错")
}
}