354 lines
11 KiB
Go
354 lines
11 KiB
Go
package web
|
|
|
|
import (
|
|
"3588AdminBackend/internal/config"
|
|
"3588AdminBackend/internal/models"
|
|
"3588AdminBackend/internal/service"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestUI_ActionDevicesBatchAction_RedirectsToTask(t *testing.T) {
|
|
cfg := &config.Config{Concurrency: 1, OfflineAfterMs: 1000000}
|
|
reg := service.NewRegistryService(cfg, nil)
|
|
reg.UpdateDevice(&models.Device{DeviceID: "dev1", IP: "127.0.0.1", AgentPort: 9100, Online: true})
|
|
reg.UpdateDevice(&models.Device{DeviceID: "dev2", IP: "127.0.0.1", AgentPort: 9100, Online: true})
|
|
tasks := service.NewTaskService(cfg, nil, reg)
|
|
|
|
ui, err := NewUI(nil, reg, nil, tasks, nil)
|
|
if err != nil {
|
|
t.Fatalf("NewUI: %v", err)
|
|
}
|
|
|
|
form := url.Values{}
|
|
form.Set("action", "media_start")
|
|
form.Add("device_id", "dev1")
|
|
form.Add("device_id", "dev2")
|
|
form.Set("config", "cam1")
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/ui/devices/batch-action", strings.NewReader(form.Encode()))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
rr := httptest.NewRecorder()
|
|
|
|
ui.actionDevicesBatchAction(rr, req)
|
|
|
|
if rr.Code != http.StatusFound {
|
|
t.Fatalf("expected 302, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
loc := rr.Header().Get("Location")
|
|
if !strings.HasPrefix(loc, "/ui/tasks/") {
|
|
t.Fatalf("expected redirect to /ui/tasks/*, got %q", loc)
|
|
}
|
|
}
|
|
|
|
func TestUI_DevicePageUsesEdgeVisionConsoleShell(t *testing.T) {
|
|
cfg := &config.Config{Concurrency: 1, OfflineAfterMs: 1000000}
|
|
reg := service.NewRegistryService(cfg, nil)
|
|
reg.UpdateDevice(&models.Device{DeviceID: "edge-01", DeviceName: "入口识别节点", IP: "127.0.0.1", AgentPort: 9100, MediaPort: 9000, Online: true})
|
|
tasks := service.NewTaskService(cfg, nil, reg)
|
|
|
|
ui, err := NewUI(nil, reg, nil, tasks, nil)
|
|
if err != nil {
|
|
t.Fatalf("NewUI: %v", err)
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/ui/devices", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
ui.pageDevices(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
body := rr.Body.String()
|
|
for _, want := range []string{
|
|
"视觉识别边缘控制台",
|
|
"设备列表",
|
|
"识别配置",
|
|
"模型管理",
|
|
"高级调试",
|
|
"<h1>设备列表</h1>",
|
|
} {
|
|
if !strings.Contains(body, want) {
|
|
t.Fatalf("expected rendered HTML to contain %q, got:\n%s", want, body)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUI_ConsoleTypographyStaysModerate(t *testing.T) {
|
|
css, err := os.ReadFile("ui/assets/style.css")
|
|
if err != nil {
|
|
t.Fatalf("read stylesheet: %v", err)
|
|
}
|
|
text := string(css)
|
|
for _, want := range []string{
|
|
".brand-title{font-weight:600",
|
|
".nav-section{font-size:11px",
|
|
".side-nav a{color:#dce6f1;display:flex;align-items:center;gap:10px;padding:9px 10px;border-radius:8px;font-size:13px;font-weight:500",
|
|
".topbar h1{margin:3px 0 0;font-size:18px;letter-spacing:0;font-weight:600",
|
|
".crumb{font-size:11px",
|
|
"input,select,textarea,button,.btn{background:#fff;color:var(--text);border:1px solid var(--border2);border-radius:7px;padding:8px 10px;outline:none;font:inherit;font-size:13px",
|
|
"button,.btn{cursor:pointer;font-size:13px;font-weight:600",
|
|
"th{color:#40546a;font-size:13px;font-weight:600",
|
|
} {
|
|
if !strings.Contains(text, want) {
|
|
t.Fatalf("expected stylesheet to contain %q", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func newTestUI(t *testing.T) *UI {
|
|
t.Helper()
|
|
cfg := &config.Config{Concurrency: 1, OfflineAfterMs: 1000000}
|
|
reg := service.NewRegistryService(cfg, nil)
|
|
reg.UpdateDevice(&models.Device{DeviceID: "edge-01", DeviceName: "入口识别节点", IP: "127.0.0.1", AgentPort: 9100, MediaPort: 9000, Online: true, Version: "1.0.0"})
|
|
tasks := service.NewTaskService(cfg, nil, reg)
|
|
ui, err := NewUI(nil, reg, nil, tasks, nil)
|
|
if err != nil {
|
|
t.Fatalf("NewUI: %v", err)
|
|
}
|
|
return ui
|
|
}
|
|
|
|
func TestUI_DashboardRendersFleetOverview(t *testing.T) {
|
|
ui := newTestUI(t)
|
|
req := httptest.NewRequest(http.MethodGet, "/ui/dashboard", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
ui.pageDashboard(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
body := rr.Body.String()
|
|
for _, want := range []string{"总览", "视觉节点态势", "视频分析概览", "最近任务", "快速入口", "执行中任务", "失败任务", "需要关注的节点", "运维工作流"} {
|
|
if !strings.Contains(body, want) {
|
|
t.Fatalf("expected dashboard HTML to contain %q, got:\n%s", want, body)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUI_DevicePageIncludesDrawerAndBatchConfirm(t *testing.T) {
|
|
ui := newTestUI(t)
|
|
req := httptest.NewRequest(http.MethodGet, "/ui/devices", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
ui.pageDevices(rr, req)
|
|
|
|
body := rr.Body.String()
|
|
for _, want := range []string{"device-drawer", "batch-confirm-panel", "openDeviceDrawer", "confirmBatchAction"} {
|
|
if !strings.Contains(body, want) {
|
|
t.Fatalf("expected device page HTML to contain %q", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUI_DeviceDetailIncludesTabs(t *testing.T) {
|
|
ui := newTestUI(t)
|
|
routes, err := ui.Routes()
|
|
if err != nil {
|
|
t.Fatalf("Routes: %v", err)
|
|
}
|
|
rr := httptest.NewRecorder()
|
|
|
|
routes.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/devices/edge-01", nil))
|
|
|
|
body := rr.Body.String()
|
|
for _, want := range []string{"device-tabs", "返回设备列表", "节点详情", "概览", "识别配置", "模型管理", "诊断日志", "运行指标", "技术信息"} {
|
|
if !strings.Contains(body, want) {
|
|
t.Fatalf("expected device detail HTML to contain %q", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUI_DeviceDetailIsReadOnly(t *testing.T) {
|
|
ui := newTestUI(t)
|
|
routes, err := ui.Routes()
|
|
if err != nil {
|
|
t.Fatalf("Routes: %v", err)
|
|
}
|
|
rr := httptest.NewRecorder()
|
|
|
|
routes.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/devices/edge-01", nil))
|
|
|
|
body := rr.Body.String()
|
|
for _, want := range []string{"视觉识别节点详情", "只读详情", "配置管理", "技术信息", "诊断日志", "运行指标"} {
|
|
if !strings.Contains(body, want) {
|
|
t.Fatalf("expected read-only device detail HTML to contain %q", want)
|
|
}
|
|
}
|
|
for _, forbidden := range []string{
|
|
`method="post"`,
|
|
`type="file"`,
|
|
"部署到设备",
|
|
"启动视频分析",
|
|
"重启视频分析",
|
|
"停止视频分析",
|
|
"重载识别服务",
|
|
"回滚识别配置",
|
|
"上传视频分析配置",
|
|
} {
|
|
if strings.Contains(body, forbidden) {
|
|
t.Fatalf("device detail should be read-only and not contain %q", forbidden)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUI_DeviceSubpagesIncludeContextNavigation(t *testing.T) {
|
|
ui := newTestUI(t)
|
|
dev := &models.Device{DeviceID: "edge-01", DeviceName: "入口识别节点", IP: "127.0.0.1", AgentPort: 9100, MediaPort: 9000, Online: true}
|
|
for _, content := range []string{"device_logs", "device_graphs", "config_ui"} {
|
|
req := httptest.NewRequest(http.MethodGet, "/ui/devices/edge-01", nil)
|
|
rr := httptest.NewRecorder()
|
|
ui.render(rr, req, content, PageData{Title: "节点子页面", Device: dev})
|
|
body := rr.Body.String()
|
|
for _, want := range []string{"device-tabs", "返回设备列表", "节点详情", "识别配置", "诊断日志", "运行指标"} {
|
|
if !strings.Contains(body, want) {
|
|
t.Fatalf("expected %s HTML to contain %q", content, want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUI_ConfigFriendlyIncludesWizard(t *testing.T) {
|
|
ui := newTestUI(t)
|
|
routes, err := ui.Routes()
|
|
if err != nil {
|
|
t.Fatalf("Routes: %v", err)
|
|
}
|
|
rr := httptest.NewRecorder()
|
|
|
|
routes.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/devices/edge-01/config-friendly", nil))
|
|
|
|
body := rr.Body.String()
|
|
for _, want := range []string{"config-wizard", "选择节点", "编辑视频通道", "预览变更", "部署结果", "data-wizard-section=\"channels\"", "下一步:编辑视频通道", "前往预览变更", "查看部署结果"} {
|
|
if !strings.Contains(body, want) {
|
|
t.Fatalf("expected config page HTML to contain %q", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUI_ModelDeploymentPageRendersDeviceActions(t *testing.T) {
|
|
ui := newTestUI(t)
|
|
req := httptest.NewRequest(http.MethodGet, "/ui/models", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
ui.pageModels(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
body := rr.Body.String()
|
|
for _, want := range []string{"模型管理", "模型管理工作流", "model-summary", "summary-value", "入口识别节点", "上传模型", "查看设备模型", "人脸库"} {
|
|
if !strings.Contains(body, want) {
|
|
t.Fatalf("expected model deployment HTML to contain %q", want)
|
|
}
|
|
}
|
|
if strings.Contains(body, `<div class="stats">
|
|
<div class="stat"><div class="k">目标节点`) {
|
|
t.Fatalf("model summary should not use large stat cards")
|
|
}
|
|
}
|
|
|
|
func TestUI_DiagnosticsPageRendersLogAndMetricLinks(t *testing.T) {
|
|
ui := newTestUI(t)
|
|
req := httptest.NewRequest(http.MethodGet, "/ui/diagnostics", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
ui.pageDiagnostics(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
body := rr.Body.String()
|
|
for _, want := range []string{"日志分析", "诊断工作台", "入口识别节点", "诊断日志", "运行指标", "高级调试"} {
|
|
if !strings.Contains(body, want) {
|
|
t.Fatalf("expected diagnostics HTML to contain %q", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUI_SidebarMatchesInformationArchitecture(t *testing.T) {
|
|
ui := newTestUI(t)
|
|
req := httptest.NewRequest(http.MethodGet, "/ui/dashboard", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
ui.pageDashboard(rr, req)
|
|
|
|
body := rr.Body.String()
|
|
for _, want := range []string{
|
|
"总览",
|
|
"设备列表",
|
|
"配置管理",
|
|
"新增设备",
|
|
"模型管理",
|
|
"识别配置",
|
|
"日志分析",
|
|
"高级调试",
|
|
`href="/ui/device-config"`,
|
|
`href="/ui/recognition"`,
|
|
`href="/ui/logs"`,
|
|
} {
|
|
if !strings.Contains(body, want) {
|
|
t.Fatalf("expected sidebar to contain %q", want)
|
|
}
|
|
}
|
|
for _, old := range []string{"设备节点", "添加节点", "模型部署", "日志指标", "任务中心"} {
|
|
if strings.Contains(body, old) {
|
|
t.Fatalf("sidebar should not contain old label %q", old)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUI_DeviceConfigPageDefinesOpsConfigScope(t *testing.T) {
|
|
ui := newTestUI(t)
|
|
req := httptest.NewRequest(http.MethodGet, "/ui/device-config", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
ui.pageDeviceConfig(rr, req)
|
|
|
|
body := rr.Body.String()
|
|
for _, want := range []string{"配置管理", "设备运行配置", "服务状态", "服务管理", "配置查看", "配置上传", "入口识别节点", "启动视频分析", "重启视频分析", "停止视频分析", "重载识别服务", "回滚识别配置"} {
|
|
if !strings.Contains(body, want) {
|
|
t.Fatalf("expected device config HTML to contain %q", want)
|
|
}
|
|
}
|
|
if !strings.Contains(body, `method="post"`) {
|
|
t.Fatalf("expected device config page to contain service operation forms")
|
|
}
|
|
}
|
|
|
|
func TestUI_RecognitionPageDefinesAIScope(t *testing.T) {
|
|
ui := newTestUI(t)
|
|
req := httptest.NewRequest(http.MethodGet, "/ui/recognition", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
ui.pageRecognition(rr, req)
|
|
|
|
body := rr.Body.String()
|
|
for _, want := range []string{"识别配置", "识别方案", "视频通道", "模板参数", "预览变更", "入口识别节点"} {
|
|
if !strings.Contains(body, want) {
|
|
t.Fatalf("expected recognition HTML to contain %q", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUI_LogsPageDefinesDiagnosticsScope(t *testing.T) {
|
|
ui := newTestUI(t)
|
|
req := httptest.NewRequest(http.MethodGet, "/ui/logs", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
ui.pageLogs(rr, req)
|
|
|
|
body := rr.Body.String()
|
|
for _, want := range []string{"日志分析", "日志筛选", "诊断日志", "运行指标", "高级调试", "入口识别节点"} {
|
|
if !strings.Contains(body, want) {
|
|
t.Fatalf("expected logs HTML to contain %q", want)
|
|
}
|
|
}
|
|
}
|