问题与需求:后台禁用员工后旧会话仍可继续访问;实时队列跨天误读昨日场次;项目暂停后需禁止取号但允许叫号。 修复思路:账号权限变更时撤销会话并同步前端登录态;实时查询统一按项目时区当天场次过滤;拆分取号与叫号的状态校验,并补充前后端及 PostgreSQL 回归测试。
143 lines
4.3 KiB
Go
143 lines
4.3 KiB
Go
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 TestDisablingStaffRevokesSessionsAndBlocksLoginPostgresIntegration(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{
|
|
EncryptionKey: bytes.Repeat([]byte{0x61}, 32),
|
|
PhoneHMACKey: bytes.Repeat([]byte{0x62}, 32),
|
|
SessionCookieName: "queue_session",
|
|
SessionTTL: time.Hour,
|
|
}, logger)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
now := time.Date(2026, 7, 30, 10, 0, 0, 0, time.UTC)
|
|
server.now = func() time.Time { return now }
|
|
|
|
password := "StaffPassword123!"
|
|
passwordHash, err := security.HashPassword(password)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
admin := model.User{
|
|
ID: uuid.NewString(), Username: "auth_admin_" + uuid.NewString()[:8],
|
|
PasswordHash: passwordHash, Role: model.RoleAdmin, Active: true,
|
|
CreatedAt: now, UpdatedAt: now,
|
|
}
|
|
staff := model.User{
|
|
ID: uuid.NewString(), Username: "auth_staff_" + uuid.NewString()[:8],
|
|
PasswordHash: passwordHash, Role: model.RoleStaff, Active: true,
|
|
CreatedAt: now, UpdatedAt: now,
|
|
}
|
|
if err := db.Create(&admin).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := db.Create(&staff).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
login := func() *httptest.ResponseRecorder {
|
|
recorder := httptest.NewRecorder()
|
|
request := httptest.NewRequest(http.MethodPost, "/api/staff/auth/login",
|
|
strings.NewReader(`{"username":"`+staff.Username+`","password":"`+password+`"}`))
|
|
server.Handler().ServeHTTP(recorder, request)
|
|
return recorder
|
|
}
|
|
|
|
firstLogin := login()
|
|
if firstLogin.Code != http.StatusOK {
|
|
t.Fatalf("initial login status = %d, want 200; body = %s", firstLogin.Code, firstLogin.Body.String())
|
|
}
|
|
var staffCookie *http.Cookie
|
|
for _, cookie := range firstLogin.Result().Cookies() {
|
|
if cookie.Name == server.authCookieName(model.RoleStaff) {
|
|
staffCookie = cookie
|
|
break
|
|
}
|
|
}
|
|
if staffCookie == nil {
|
|
t.Fatal("initial login did not set the staff session cookie")
|
|
}
|
|
|
|
updateRequest := httptest.NewRequest(http.MethodPut, "/api/admin/users/"+staff.ID,
|
|
strings.NewReader(`{"role":"STAFF","active":false,"project_ids":[]}`))
|
|
updateRequest.SetPathValue("id", staff.ID)
|
|
updateRequest = updateRequest.WithContext(context.WithValue(updateRequest.Context(), principalKey, principal{User: admin}))
|
|
updateRecorder := httptest.NewRecorder()
|
|
server.updateAdminUser(updateRecorder, updateRequest)
|
|
if updateRecorder.Code != http.StatusOK {
|
|
t.Fatalf("disable status = %d, want 200; body = %s", updateRecorder.Code, updateRecorder.Body.String())
|
|
}
|
|
|
|
var disabledStaff model.User
|
|
if err := db.First(&disabledStaff, "id = ?", staff.ID).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if disabledStaff.Active {
|
|
t.Error("staff remains active after disabling")
|
|
}
|
|
|
|
var activeSessions int64
|
|
if err := db.Model(&model.AuthSession{}).
|
|
Where("user_id = ? AND revoked_at IS NULL", staff.ID).
|
|
Count(&activeSessions).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if activeSessions != 0 {
|
|
t.Errorf("active sessions after disabling staff = %d, want 0", activeSessions)
|
|
}
|
|
|
|
meRecorder := httptest.NewRecorder()
|
|
meRequest := httptest.NewRequest(http.MethodGet, "/api/staff/auth/me", nil)
|
|
meRequest.AddCookie(staffCookie)
|
|
server.Handler().ServeHTTP(meRecorder, meRequest)
|
|
if meRecorder.Code != http.StatusUnauthorized {
|
|
t.Errorf("existing session after disabling status = %d, want 401; body = %s", meRecorder.Code, meRecorder.Body.String())
|
|
}
|
|
|
|
secondLogin := login()
|
|
if secondLogin.Code != http.StatusUnauthorized {
|
|
t.Fatalf("login after disabling status = %d, want 401; body = %s", secondLogin.Code, secondLogin.Body.String())
|
|
}
|
|
}
|