Files
XQKqueue/server/internal/httpapi/history_test.go
2026-07-22 10:18:49 +08:00

79 lines
3.3 KiB
Go

package httpapi
import (
"net/http/httptest"
"testing"
"time"
)
func TestParseHistoryFilterUsesRecentSevenDaysByDefault(t *testing.T) {
server := &Server{now: func() time.Time { return time.Date(2026, 7, 16, 8, 0, 0, 0, time.UTC) }}
filter, err := server.parseHistoryFilter(httptest.NewRequest("GET", "/api/admin/history/tickets", nil), historyTicketStatuses)
if err != nil {
t.Fatal(err)
}
if filter.From != "2026-07-10" || filter.To != "2026-07-16" || filter.Page != 1 || filter.PageSize != 20 {
t.Fatalf("default history filter = %#v", filter)
}
}
func TestParseHistoryFilterRejectsUnsafeRangeAndPageSize(t *testing.T) {
server := &Server{now: time.Now}
for _, path := range []string{
"/api/admin/history/tickets?from=2026-01-01&to=2026-02-01",
"/api/admin/history/tickets?from=2026-07-10&to=2026-07-16&page_size=101",
} {
if _, err := server.parseHistoryFilter(httptest.NewRequest("GET", path, nil), historyTicketStatuses); err == nil {
t.Fatalf("expected history filter to be rejected: %s", path)
}
}
}
func TestHistoryTicketViewMasksPersonalDataAndHonorsAnonymization(t *testing.T) {
server, ticket := encryptedTicketFixture(t)
row := historyTicketRow{
ID: ticket.ID, ProjectID: ticket.ProjectID, ProjectName: "云岭漂流", ProjectCode: "RIDE",
QueueSessionID: "session-a", BusinessDate: time.Date(2026, 7, 16, 0, 0, 0, 0, time.UTC),
TicketNumber: 42, DisplayNumber: ticket.DisplayNumber, PartySize: ticket.PartySize, Status: ticket.Status,
JoinedAt: ticket.JoinedAt, PhoneCiphertext: ticket.PhoneCiphertext, PhoneNonce: ticket.PhoneNonce, PhoneHMAC: ticket.PhoneHMAC,
LastNameCiphertext: ticket.LastNameCiphertext, LastNameNonce: ticket.LastNameNonce, Honorific: ticket.Honorific,
}
masked, err := server.historyTicketView(row, false)
if err != nil {
t.Fatal(err)
}
if masked["phone"] != nil || masked["last_name"] != nil || masked["phone_masked"] != "****5678" {
t.Fatalf("masked history view leaked personal data: %#v", masked)
}
revealed, err := server.historyTicketView(row, true)
if err != nil || revealed["phone"] != "+8613812345678" || revealed["last_name"] != "林" {
t.Fatalf("revealed history view = %#v, err = %v", revealed, err)
}
marked := time.Date(2026, 10, 14, 0, 0, 0, 0, time.UTC)
row.HistoryAnonymizedAt = &marked
anonymized, err := server.historyTicketView(row, true)
if err != nil {
t.Fatal(err)
}
if anonymized["personal_data_status"] != "ANONYMIZED" || anonymized["phone"] != nil || anonymized["phone_masked"] != nil {
t.Fatalf("anonymized history view = %#v", anonymized)
}
if anonymized["honorific"] != nil {
t.Fatalf("anonymized history view leaked honorific: %#v", anonymized)
}
}
func TestHistorySummaryAlertsUseMinimumSampleAndOperationalThresholds(t *testing.T) {
longWait := 15.0 * 60
alerts := historySummaryAlerts([]historyProjectRow{
{ProjectID: "project-alert", ProjectName: "峡谷漂流", TotalTickets: 10, MissedTickets: 3, CompletedTickets: 6, AverageWait: &longWait},
{ProjectID: "project-small", ProjectName: "小样本项目", TotalTickets: 4, MissedTickets: 4, CompletedTickets: 0, AverageWait: &longWait},
})
if len(alerts) != 1 {
t.Fatalf("alerts = %#v, want one alert after minimum sample filtering", alerts)
}
if alerts[0]["type"] != "MISSED_RATE" || alerts[0]["project_id"] != "project-alert" {
t.Fatalf("alert = %#v", alerts[0])
}
}