Source-only publication of reviewed commit 64e96a7f423673dc29ad56485be9fc79bce8af1c.
Passed in isolation: focused frontend/backend tests, race checks, TypeScript typecheck, lint, Go compilation, Flowgram build, and deterministic SDK build/tests.
Not accepted as a production release: Next.js export/native assembly remains blocked by environment memory limits; browser interaction and MySQL staging acceptance remain unverified. No production server, database/schema, runtime configuration, deployment, release tag, or workflow execution was performed.
Expected source tree: 0a4a7528e9.
122 lines
4.8 KiB
Go
122 lines
4.8 KiB
Go
package dashboard
|
|
|
|
import (
|
|
"agent-desk/internal/models"
|
|
"agent-desk/internal/pkg/constants"
|
|
"agent-desk/internal/pkg/dto"
|
|
"agent-desk/internal/pkg/enums"
|
|
"encoding/json"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/glebarez/sqlite"
|
|
"github.com/mlogclub/simple/sqls"
|
|
"gorm.io/gorm"
|
|
"net/http/httptest"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func availabilityHandlerDB(t *testing.T) *gorm.DB {
|
|
t.Helper()
|
|
db, err := gorm.Open(sqlite.Open(filepath.Join(t.TempDir(), "availability.db")), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := db.AutoMigrate(&models.User{}, &models.AgentProfile{}, &models.AgentTeam{}, &models.AgentTeamSchedule{}, &models.SupportCalendar{}, &models.AgentPresenceLease{}, &models.Conversation{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
previous := sqls.DB()
|
|
sqls.SetDB(db)
|
|
t.Cleanup(func() { sqls.SetDB(previous); conn, _ := db.DB(); conn.Close() })
|
|
for _, row := range []any{&models.User{ID: 1, Username: "one", UserType: enums.UserTypeEmployee}, &models.User{ID: 2, Username: "two", UserType: enums.UserTypeEmployee}, &models.AgentTeam{ID: 1, Name: "Support"}, &models.AgentProfile{ID: 1, UserID: 1, TeamID: 1, AgentCode: "one"}, &models.AgentProfile{ID: 2, UserID: 2, TeamID: 1, AgentCode: "two"}} {
|
|
if err := db.Create(row).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
return db
|
|
}
|
|
func callAvailabilityHandler(t *testing.T, handler gin.HandlerFunc, principal *dto.AuthPrincipal, body string) map[string]any {
|
|
t.Helper()
|
|
gin.SetMode(gin.TestMode)
|
|
router := gin.New()
|
|
router.Use(func(c *gin.Context) {
|
|
if principal != nil {
|
|
c.Set("authPrincipal", principal)
|
|
}
|
|
c.Next()
|
|
})
|
|
router.POST("/test", handler)
|
|
req := httptest.NewRequest("POST", "/test", strings.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
router.ServeHTTP(rec, req)
|
|
var result map[string]any
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &result); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return result
|
|
}
|
|
func TestSupportAvailabilitySelfCannotTargetAnotherUser(t *testing.T) {
|
|
db := availabilityHandlerDB(t)
|
|
principal := &dto.AuthPrincipal{UserID: 1, Username: "one", UserType: enums.UserTypeEmployee}
|
|
got := callAvailabilityHandler(t, SupportAvailabilityPostMe, principal, `{"userId":2,"serviceStatus":2}`)
|
|
if got["success"] != true {
|
|
t.Fatalf("self update: %v", got)
|
|
}
|
|
var own, other models.AgentProfile
|
|
db.First(&own, 1)
|
|
db.First(&other, 2)
|
|
if own.ServiceStatus != enums.ServiceStatusBreak || other.ServiceStatus != enums.ServiceStatusIdle {
|
|
t.Fatal("self endpoint changed another profile")
|
|
}
|
|
}
|
|
func TestSupportAvailabilitySelfDoesNotProvisionMissingProfile(t *testing.T) {
|
|
db := availabilityHandlerDB(t)
|
|
principal := &dto.AuthPrincipal{UserID: 99, Username: "missing", UserType: enums.UserTypeEmployee}
|
|
got := callAvailabilityHandler(t, SupportAvailabilityPostMe, principal, `{"serviceStatus":0}`)
|
|
if got["success"] == true {
|
|
t.Fatal("missing operator gained profile")
|
|
}
|
|
var count int64
|
|
db.Model(&models.AgentProfile{}).Count(&count)
|
|
if count != 2 {
|
|
t.Fatalf("profile count %d", count)
|
|
}
|
|
}
|
|
func TestSupportAvailabilityAdminRequiresExactPermission(t *testing.T) {
|
|
db := availabilityHandlerDB(t)
|
|
principal := &dto.AuthPrincipal{UserID: 1, Username: "one", Permissions: []string{constants.PermissionAgentView.Code}}
|
|
got := callAvailabilityHandler(t, SupportAvailabilityPostAgent, principal, `{"userId":2,"serviceStatus":3}`)
|
|
if got["success"] == true {
|
|
t.Fatal("read permission changed another operator")
|
|
}
|
|
var other models.AgentProfile
|
|
db.First(&other, 2)
|
|
if other.ServiceStatus != enums.ServiceStatusIdle {
|
|
t.Fatal("unauthorized mutation")
|
|
}
|
|
principal.Permissions = []string{constants.PermissionAgentUpdate.Code}
|
|
got = callAvailabilityHandler(t, SupportAvailabilityPostAgent, principal, `{"userId":2,"serviceStatus":3}`)
|
|
if got["success"] != true {
|
|
t.Fatalf("authorized update: %v", got)
|
|
}
|
|
}
|
|
func TestSupportCalendarDeploymentAndTeamPermissionsDoNotOverlap(t *testing.T) {
|
|
db := availabilityHandlerDB(t)
|
|
principal := &dto.AuthPrincipal{UserID: 1, Username: "one", Permissions: []string{constants.PermissionAgentTeamScheduleUpdate.Code}}
|
|
got := callAvailabilityHandler(t, SupportAvailabilityPostCalendar, principal, `{"teamId":0,"mode":"recurring","timezone":"Europe/Berlin","weekly":[],"exceptions":[]}`)
|
|
if got["success"] == true {
|
|
t.Fatal("team editor changed deployment default")
|
|
}
|
|
principal.Permissions = []string{constants.PermissionSupportConfigUpdate.Code}
|
|
got = callAvailabilityHandler(t, SupportAvailabilityPostCalendar, principal, `{"teamId":1,"mode":"recurring","timezone":"Europe/Berlin","weekly":[],"exceptions":[]}`)
|
|
if got["success"] == true {
|
|
t.Fatal("deployment editor changed team schedule")
|
|
}
|
|
var count int64
|
|
db.Model(&models.SupportCalendar{}).Count(&count)
|
|
if count != 0 {
|
|
t.Fatal("denied mutations persisted")
|
|
}
|
|
}
|