修复bug及添加日志打印
This commit is contained in:
133
server/internal/httpapi/public_integration_test.go
Normal file
133
server/internal/httpapi/public_integration_test.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"calllinesystem/server/internal/config"
|
||||
"calllinesystem/server/internal/database"
|
||||
"calllinesystem/server/internal/model"
|
||||
"calllinesystem/server/internal/security"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func TestPublicCreateTicketIgnoresDuplicateFromEndedSession(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: "development",
|
||||
EncryptionKey: bytes.Repeat([]byte{0x41}, 32),
|
||||
PhoneHMACKey: bytes.Repeat([]byte{0x42}, 32),
|
||||
}, logger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
now := time.Date(2026, 7, 28, 1, 0, 0, 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("PUB" + uuid.NewString()[:6]), Name: "Public 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)
|
||||
}
|
||||
endedAt := now.Add(-12 * time.Hour)
|
||||
oldSession := model.QueueSession{
|
||||
ID: uuid.NewString(), ProjectID: projectID, BusinessDate: now.AddDate(0, 0, -1),
|
||||
Status: "ENDED", NextTicketNumber: 2, Revision: 1,
|
||||
OpenedAt: now.Add(-24 * time.Hour), ClosedAt: &endedAt, CreatedAt: now.Add(-24 * time.Hour), UpdatedAt: endedAt,
|
||||
}
|
||||
currentSession := model.QueueSession{
|
||||
ID: uuid.NewString(), ProjectID: projectID, BusinessDate: now,
|
||||
Status: "RUNNING", NextTicketNumber: 1, Revision: 0,
|
||||
OpenedAt: now, CreatedAt: now, UpdatedAt: now,
|
||||
}
|
||||
if err := db.Create(&oldSession).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.Create(¤tSession).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
phone := "13800138000"
|
||||
phoneCiphertext, phoneNonce, err := server.cipher.Encrypt(phone, []byte("phone:"+projectID))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
phoneHMAC := server.cipher.Digest(phone)
|
||||
oldTicket := model.QueueTicket{
|
||||
ID: uuid.NewString(), ProjectID: projectID, QueueSessionID: oldSession.ID,
|
||||
TicketNumber: 1, DisplayNumber: "00001", PartySize: 1,
|
||||
PublicTokenHash: security.HashToken(uuid.NewString()),
|
||||
PhoneCiphertext: phoneCiphertext, PhoneNonce: phoneNonce, PhoneHMAC: &phoneHMAC,
|
||||
Honorific: "游客", Status: model.TicketWaiting, JoinedAt: oldSession.OpenedAt,
|
||||
PersonalDataPurgeAt: now.Add(30 * 24 * time.Hour), CreatedBy: actor.ID,
|
||||
CreatedAt: oldSession.OpenedAt, UpdatedAt: oldSession.OpenedAt,
|
||||
}
|
||||
if err := db.Create(&oldTicket).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodPost, "/api/public/projects/"+projectID+"/tickets",
|
||||
strings.NewReader(`{"phone":"13800138000","honorific":"游客","party_size":1,"allow_duplicate":false}`))
|
||||
request.Header.Set("Idempotency-Key", "public-ended-session-"+uuid.NewString())
|
||||
|
||||
server.Handler().ServeHTTP(recorder, request)
|
||||
|
||||
if recorder.Code != http.StatusCreated {
|
||||
t.Fatalf("status = %d, want 201; body = %s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
|
||||
duplicateRecorder := httptest.NewRecorder()
|
||||
duplicateRequest := httptest.NewRequest(http.MethodPost, "/api/public/projects/"+projectID+"/tickets",
|
||||
strings.NewReader(`{"phone":"13800138000","honorific":"游客","party_size":1,"allow_duplicate":false}`))
|
||||
duplicateRequest.Header.Set("Idempotency-Key", "public-current-session-"+uuid.NewString())
|
||||
|
||||
server.Handler().ServeHTTP(duplicateRecorder, duplicateRequest)
|
||||
|
||||
if duplicateRecorder.Code != http.StatusConflict || !strings.Contains(duplicateRecorder.Body.String(), `"code":"DUPLICATE_PHONE"`) {
|
||||
t.Fatalf("current-session duplicate status = %d, want 409 DUPLICATE_PHONE; body = %s",
|
||||
duplicateRecorder.Code, duplicateRecorder.Body.String())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user