修改场次bug
This commit is contained in:
@@ -115,14 +115,22 @@ func (s *Server) statusByPhone(w http.ResponseWriter, r *http.Request, limitByCl
|
||||
}
|
||||
|
||||
var tickets []model.QueueTicket
|
||||
activeSessionStatuses := []string{"RUNNING", "PAUSED"}
|
||||
latestActiveSession := s.db.Table("queue_sessions AS latest_active_session").
|
||||
Select("latest_active_session.id").
|
||||
Where("latest_active_session.project_id = queue_tickets.project_id").
|
||||
Where("latest_active_session.status IN ?", activeSessionStatuses).
|
||||
Order("latest_active_session.business_date DESC").
|
||||
Limit(1)
|
||||
if err := s.db.WithContext(r.Context()).Model(&model.QueueTicket{}).
|
||||
Joins("JOIN projects ON projects.id = queue_tickets.project_id").
|
||||
Joins("JOIN queue_sessions ON queue_sessions.id = queue_tickets.queue_session_id AND queue_sessions.project_id = queue_tickets.project_id").
|
||||
Where("queue_tickets.phone_hmac = ? AND queue_tickets.status IN ? AND queue_sessions.status IN ? AND projects.status IN ?",
|
||||
phoneDigest,
|
||||
[]string{model.TicketWaiting, model.TicketCalled, model.TicketArrived},
|
||||
[]string{"RUNNING", "PAUSED"},
|
||||
activeSessionStatuses,
|
||||
[]string{model.ProjectRunning, model.ProjectPaused}).
|
||||
Where("queue_tickets.queue_session_id = (?)", latestActiveSession).
|
||||
Order("projects.name ASC, queue_tickets.ticket_number ASC").
|
||||
Find(&tickets).Error; err != nil {
|
||||
writeError(w, err)
|
||||
|
||||
@@ -3,6 +3,7 @@ package httpapi
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
@@ -131,3 +132,132 @@ func TestPublicCreateTicketIgnoresDuplicateFromEndedSession(t *testing.T) {
|
||||
duplicateRecorder.Code, duplicateRecorder.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestInternalPhoneLookupUsesLatestActiveSessionPerProject(t *testing.T) {
|
||||
dsn := strings.TrimSpace(os.Getenv("TEST_DATABASE_URL"))
|
||||
if dsn == "" {
|
||||
t.Skip("TEST_DATABASE_URL is not set")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
db, err := database.Open(ctx, dsn, logger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer database.Close(db)
|
||||
sqlDB, err := database.SQLDB(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := database.Migrate(ctx, sqlDB, logger); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
server, err := New(db, config.Config{
|
||||
Environment: "production",
|
||||
EncryptionKey: bytes.Repeat([]byte{0x51}, 32),
|
||||
PhoneHMACKey: bytes.Repeat([]byte{0x52}, 32),
|
||||
}, logger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
now := time.Date(2026, 7, 28, 2, 42, 35, 0, time.UTC)
|
||||
server.now = func() time.Time { return now }
|
||||
|
||||
var actor model.User
|
||||
if err := db.Where("username = ?", model.PublicVisitorUsername).First(&actor).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
projectID := uuid.NewString()
|
||||
project := model.Project{
|
||||
ID: projectID, Code: strings.ToUpper("LOOK" + uuid.NewString()[:6]), Name: "Phone lookup duplicate regression",
|
||||
Status: model.ProjectRunning, Timezone: "Asia/Shanghai", TicketPrefix: "A",
|
||||
CallBatchSize: 5, CallMode: model.CallModeBoth,
|
||||
MaxCallTicketCount: 100, DefaultCallPeopleCount: 1, MaxCallPeopleCount: 100,
|
||||
MinPartySize: 1, MaxPartySize: 10, GracePeriodMinutes: 5,
|
||||
ETAMode: model.ETAFixedBatch, AverageBatchIntervalSeconds: 60,
|
||||
ContinuousRatePerMinute: 2, ETABufferMinutes: 5, ETAIntervalSeconds: 60,
|
||||
VisitorNotice: "", DeviceSimulationMode: "DISABLED", CreatedAt: now, UpdatedAt: now,
|
||||
}
|
||||
if err := db.Create(&project).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
staleSession := model.QueueSession{
|
||||
ID: uuid.NewString(), ProjectID: projectID, BusinessDate: now.AddDate(0, 0, -1),
|
||||
Status: "RUNNING", NextTicketNumber: 2, Revision: 1,
|
||||
OpenedAt: now.Add(-24 * time.Hour), CreatedAt: now.Add(-24 * time.Hour), UpdatedAt: now.Add(-24 * time.Hour),
|
||||
}
|
||||
currentSession := model.QueueSession{
|
||||
ID: uuid.NewString(), ProjectID: projectID, BusinessDate: now,
|
||||
Status: "RUNNING", NextTicketNumber: 3, Revision: 3,
|
||||
OpenedAt: now, CreatedAt: now, UpdatedAt: now,
|
||||
}
|
||||
if err := db.Create(&staleSession).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.Create(¤tSession).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
phone := "18286070628"
|
||||
phoneCiphertext, phoneNonce, err := server.cipher.Encrypt(phone, []byte("phone:"+projectID))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
phoneHMAC := server.cipher.Digest(phone)
|
||||
ticketFixtures := []struct {
|
||||
session model.QueueSession
|
||||
ticketNumber int
|
||||
displayNumber string
|
||||
}{
|
||||
{session: staleSession, ticketNumber: 1, displayNumber: "00001"},
|
||||
{session: currentSession, ticketNumber: 1, displayNumber: "00001"},
|
||||
{session: currentSession, ticketNumber: 2, displayNumber: "00002"},
|
||||
}
|
||||
for _, fixture := range ticketFixtures {
|
||||
ticket := model.QueueTicket{
|
||||
ID: uuid.NewString(), ProjectID: projectID, QueueSessionID: fixture.session.ID,
|
||||
TicketNumber: fixture.ticketNumber, DisplayNumber: fixture.displayNumber, PartySize: 1,
|
||||
PublicTokenHash: security.HashToken(uuid.NewString()),
|
||||
PhoneCiphertext: phoneCiphertext, PhoneNonce: phoneNonce, PhoneHMAC: &phoneHMAC,
|
||||
Honorific: "游客", Status: model.TicketWaiting, JoinedAt: fixture.session.OpenedAt,
|
||||
PersonalDataPurgeAt: now.Add(30 * 24 * time.Hour), CreatedBy: actor.ID,
|
||||
CreatedAt: fixture.session.OpenedAt, UpdatedAt: fixture.session.OpenedAt,
|
||||
}
|
||||
if err := db.Create(&ticket).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodPost, "/api/internal/status/search", strings.NewReader(`{"phone":"18286070628"}`))
|
||||
request.RemoteAddr = "10.23.4.5:42000"
|
||||
server.Handler().ServeHTTP(recorder, request)
|
||||
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200; body = %s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
var response struct {
|
||||
Tickets []struct {
|
||||
TicketNumber string `json:"ticket_number"`
|
||||
Revision int64 `json:"revision"`
|
||||
} `json:"tickets"`
|
||||
}
|
||||
if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(response.Tickets) != 2 {
|
||||
t.Fatalf("tickets = %#v, want only the two latest-session tickets", response.Tickets)
|
||||
}
|
||||
if response.Tickets[0].TicketNumber != "00001" || response.Tickets[1].TicketNumber != "00002" {
|
||||
t.Fatalf("tickets = %#v, want distinct current-session tickets 00001 and 00002", response.Tickets)
|
||||
}
|
||||
for _, ticket := range response.Tickets {
|
||||
if ticket.Revision != currentSession.Revision {
|
||||
t.Fatalf("ticket = %#v, want current session revision %d", ticket, currentSession.Revision)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user