package logic import ( "strings" "testing" "github.com/gogf/gf/v2/errors/gerror" "tool-api/internal/consts" ) // ============================================================================ // T04 自动排座「排除」语义单测(纯函数,不依赖 DB) // // 覆盖: // - 排除桌上的座位/人员完全不被触碰(不分配、不清手工号、不计 manual_overridden); // - fail-closed:全部桌被排除 → 4018 且 message 列出被排除的桌; // - 部分排除 / 无桌 → 放行。 // ============================================================================ // TestPlanAutoAssignExcludesTable 排除桌的座位不参与分配,且不计入 manual_overridden。 func TestPlanAutoAssignExcludesTable(t *testing.T) { seats := []seatBrief{ // 1 号桌:参与 {Id: 1, TableId: 1, SeatNo: 1, EmployeeId: 0}, {Id: 2, TableId: 1, SeatNo: 2, IsManual: true, EmployeeId: 0}, // 参与桌的手工位:reorder 可用 // 2 号桌:被排除(不参与自动排序) {Id: 3, TableId: 2, SeatNo: 1, IsManual: true, EmployeeId: 0, Excluded: true}, // 排除桌的手工空位 {Id: 4, TableId: 2, SeatNo: 2, EmployeeId: 0, Excluded: true}, // 排除桌的普通空位 } assigns, remaining, overridden := planAutoAssign(seats, []int64{101, 102, 103}, consts.SeatModeReorder) // 排除桌 id=3、id=4 一律不可被占用 for _, a := range assigns { if a.SeatId == 3 || a.SeatId == 4 { t.Fatalf("排除桌的座位不应被分配,got %v", assigns) } } // 参与桌有 2 个可用位(id=1、id=2),故只分配 2 人,剩 1 人 if len(assigns) != 2 { t.Fatalf("应分配 2 人(仅参与桌),got %d (%v)", len(assigns), assigns) } if len(remaining) != 1 || remaining[0] != 103 { t.Fatalf("remaining=%v want [103]", remaining) } // manual_overridden 只统计**参与桌**的手工位(id=2),排除桌的手工位(id=3)不计入 if overridden != 1 { t.Fatalf("排除桌的手工位不应计入 manual_overridden,got %d want 1", overridden) } } // TestPlanAutoAssignExcludesTableFill 排除桌在 fill 模式下同样不被触碰。 func TestPlanAutoAssignExcludesTableFill(t *testing.T) { seats := []seatBrief{ {Id: 1, TableId: 1, SeatNo: 1, EmployeeId: 0}, {Id: 2, TableId: 2, SeatNo: 1, EmployeeId: 0, Excluded: true}, } assigns, _, _ := planAutoAssign(seats, []int64{101, 102}, consts.SeatModeFill) if len(assigns) != 1 || assigns[0].SeatId != 1 { t.Fatalf("fill 应只填参与桌 id=1,got %v", assigns) } } // TestPlanAutoGuardsAllExcluded 全部桌被排除 → 4018,且 message 列出被排除的桌。 func TestPlanAutoGuardsAllExcluded(t *testing.T) { tables := []tableBrief{ {Id: 1, Name: "主桌", ExcludeAuto: 1}, {Id: 2, TableNo: "2", ExcludeAuto: 1}, } err := planAutoGuards(tables) if err == nil { t.Fatalf("全部桌被排除应报错(fail-closed)") } if code := gerror.Code(err).Code(); code != consts.CodeSeatAutoInvalid { t.Fatalf("错误码应为 %d,got %d", consts.CodeSeatAutoInvalid, code) } msg := err.Error() if !strings.Contains(msg, "主桌") || !strings.Contains(msg, "2 号桌") { t.Fatalf("错误信息应列出被排除的桌,got: %s", msg) } } // TestPlanAutoGuardsPartialExcluded 部分桌可参与 → 放行(含仅 1 张桌可参与)。 func TestPlanAutoGuardsPartialExcluded(t *testing.T) { tables := []tableBrief{ {Id: 1, Name: "签到台", ExcludeAuto: 1}, {Id: 2, Name: "1 号桌", ExcludeAuto: 0}, // 仅此 1 张可参与 → 允许 } if err := planAutoGuards(tables); err != nil { t.Fatalf("有 1 张可参与桌时应放行,got %v", err) } } // TestPlanAutoGuardsNoTables 无桌 → 放行(交由后续逻辑自然得到 0 结果,保持老行为)。 func TestPlanAutoGuardsNoTables(t *testing.T) { if err := planAutoGuards(nil); err != nil { t.Fatalf("无桌不应触发 fail-closed,got %v", err) } } // TestTableDisplayName 桌名回退:Name > 桌号 > id。 func TestTableDisplayName(t *testing.T) { if got := tableDisplayName(tableBrief{Id: 1, Name: "主桌", TableNo: "1"}); got != "主桌" { t.Fatalf("应优先用 Name,got %q", got) } if got := tableDisplayName(tableBrief{Id: 2, TableNo: "3"}); got != "3 号桌" { t.Fatalf("无 Name 时用「桌号 号桌」,got %q", got) } if got := tableDisplayName(tableBrief{Id: 7}); got != "桌#7" { t.Fatalf("无桌名桌号时回退 id,got %q", got) } }