feat: harden support, localize knowledge and add operator availability [skip ci]

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.
This commit is contained in:
2026-10-16 15:21:42 +00:00
parent 299ea50fb5
commit 2f7a491666
176 changed files with 11869 additions and 2716 deletions

View File

@@ -0,0 +1,121 @@
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")
}
}