test: add 9 integration tests against live safesightd API
This commit is contained in:
parent
5433065925
commit
b256ecc5d2
265
internal/service/integration_test.go
Normal file
265
internal/service/integration_test.go
Normal file
@ -0,0 +1,265 @@
|
||||
//go:build integration
|
||||
// +build integration
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Integration tests against a running safesightd instance.
|
||||
// Set SAFESIGHT_URL env (default http://10.0.0.81:18080).
|
||||
// Run with: go test -tags=integration -v -count=1 ./internal/service/
|
||||
|
||||
func safeSightURL() string {
|
||||
if u := os.Getenv("SAFESIGHT_URL"); u != "" {
|
||||
return strings.TrimRight(u, "/")
|
||||
}
|
||||
return "http://10.0.0.81:18080"
|
||||
}
|
||||
|
||||
func apiGet(t *testing.T, path string) (*http.Response, []byte) {
|
||||
t.Helper()
|
||||
resp, err := http.Get(safeSightURL() + path)
|
||||
if err != nil {
|
||||
t.Fatalf("GET %s: %v", path, err)
|
||||
}
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
return resp, body
|
||||
}
|
||||
|
||||
func apiPost(t *testing.T, path string, payload any) (*http.Response, []byte) {
|
||||
t.Helper()
|
||||
var bodyReader io.Reader
|
||||
if payload != nil {
|
||||
b, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal: %v", err)
|
||||
}
|
||||
bodyReader = bytes.NewReader(b)
|
||||
}
|
||||
resp, err := http.Post(safeSightURL()+path, "application/json", bodyReader)
|
||||
if err != nil {
|
||||
t.Fatalf("POST %s: %v", path, err)
|
||||
}
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
return resp, respBody
|
||||
}
|
||||
|
||||
func apiPostForm(t *testing.T, path string, form map[string]string) (*http.Response, []byte) {
|
||||
t.Helper()
|
||||
data := make([]string, 0, len(form))
|
||||
for k, v := range form {
|
||||
data = append(data, k+"="+v)
|
||||
}
|
||||
body := strings.NewReader(strings.Join(data, "&"))
|
||||
resp, err := http.Post(safeSightURL()+path, "application/x-www-form-urlencoded", body)
|
||||
if err != nil {
|
||||
t.Fatalf("POST %s: %v", path, err)
|
||||
}
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
return resp, respBody
|
||||
}
|
||||
|
||||
func getDeviceID(t *testing.T) string {
|
||||
t.Helper()
|
||||
resp, body := apiGet(t, "/devices")
|
||||
if resp.StatusCode != 200 {
|
||||
return ""
|
||||
}
|
||||
page := string(body)
|
||||
skip := map[string]bool{"batch-config": true, "cleanup": true, "export-channels": true, "add": true, "config": true}
|
||||
for _, line := range strings.Split(page, "\n") {
|
||||
if idx := strings.Index(line, "href=\"/devices/"); idx >= 0 {
|
||||
rest := line[idx+len("href=\"/devices/"):]
|
||||
if end := strings.Index(rest, "\""); end > 10 {
|
||||
id := rest[:end]
|
||||
if !skip[id] {
|
||||
return id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── Health check ──
|
||||
|
||||
func TestIntegration_HealthCheck(t *testing.T) {
|
||||
resp, body := apiGet(t, "/dashboard")
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("health check failed: %d — %s", resp.StatusCode, string(body))
|
||||
}
|
||||
t.Logf("safesightd running, page size=%d bytes", len(body))
|
||||
}
|
||||
|
||||
// ── Device listing ──
|
||||
|
||||
func TestIntegration_ListDevices(t *testing.T) {
|
||||
resp, _ := apiGet(t, "/devices")
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("GET /devices: %d", resp.StatusCode)
|
||||
}
|
||||
t.Logf("devices page OK")
|
||||
}
|
||||
|
||||
// ── Template listing ──
|
||||
|
||||
func TestIntegration_ListTemplates(t *testing.T) {
|
||||
resp, body := apiGet(t, "/assets/templates")
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("GET /assets/templates: %d", resp.StatusCode)
|
||||
}
|
||||
page := string(body)
|
||||
for _, name := range []string{"std_workshop_face_recognition_shoe_alarm"} {
|
||||
if !strings.Contains(page, name) {
|
||||
t.Errorf("template %q not found", name)
|
||||
}
|
||||
}
|
||||
t.Logf("templates page OK")
|
||||
}
|
||||
|
||||
// ── Overlay asset listing ──
|
||||
|
||||
func TestIntegration_ListOverlays(t *testing.T) {
|
||||
resp, _ := apiGet(t, "/assets/overlays")
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("GET /assets/overlays: %d", resp.StatusCode)
|
||||
}
|
||||
t.Logf("overlays page OK")
|
||||
}
|
||||
|
||||
// ── Console page ──
|
||||
|
||||
func TestIntegration_ConsolePage(t *testing.T) {
|
||||
resp, _ := apiGet(t, "/console")
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("GET /console: %d", resp.StatusCode)
|
||||
}
|
||||
t.Logf("console page OK")
|
||||
}
|
||||
|
||||
// ── Alarm center ──
|
||||
|
||||
func TestIntegration_AlarmsPage(t *testing.T) {
|
||||
resp, _ := apiGet(t, "/alarms")
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("GET /alarms: %d", resp.StatusCode)
|
||||
}
|
||||
t.Logf("alarms page OK")
|
||||
}
|
||||
|
||||
// ── Device detail page with overlay section ──
|
||||
|
||||
func TestIntegration_DeviceDetailWithOverlays(t *testing.T) {
|
||||
deviceID := getDeviceID(t)
|
||||
if deviceID == "" {
|
||||
t.Skip("no device found")
|
||||
}
|
||||
|
||||
resp, body := apiGet(t, "/devices/"+deviceID)
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("GET device detail: %d", resp.StatusCode)
|
||||
}
|
||||
if !strings.Contains(string(body), "调试参数覆盖") {
|
||||
t.Error("missing '调试参数覆盖' section")
|
||||
}
|
||||
t.Logf("device %s detail page OK", deviceID)
|
||||
}
|
||||
|
||||
// ── Template settings page ──
|
||||
|
||||
func TestIntegration_TemplateSettingsPage(t *testing.T) {
|
||||
resp, body := apiGet(t, "/assets/templates")
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("GET templates: %d", resp.StatusCode)
|
||||
}
|
||||
page := string(body)
|
||||
|
||||
// Find user template (cloned from standard, no std_ prefix but with "workshop" or "face" pattern)
|
||||
userTpl := ""
|
||||
for _, candidate := range []string{"workshop_face_recognition_shoe_alarm", "face_recognition_stream"} {
|
||||
if strings.Contains(page, `">`+candidate+`<`) {
|
||||
userTpl = candidate
|
||||
break
|
||||
}
|
||||
}
|
||||
if userTpl == "" {
|
||||
t.Skip("no user template found — deploy via wizard first")
|
||||
}
|
||||
|
||||
resp, body = apiGet(t, "/assets/templates/" + userTpl + "/settings")
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("GET settings: %d", resp.StatusCode)
|
||||
}
|
||||
if !strings.Contains(string(body), "参数调整") {
|
||||
t.Error("missing '参数调整' on settings page")
|
||||
}
|
||||
t.Logf("template %s settings page OK", userTpl)
|
||||
}
|
||||
|
||||
// ── Wizard deploy roundtrip ──
|
||||
|
||||
func TestIntegration_WizardDeployRoundtrip(t *testing.T) {
|
||||
deviceID := getDeviceID(t)
|
||||
if deviceID == "" {
|
||||
t.Skip("no device found")
|
||||
}
|
||||
t.Logf("device: %s", deviceID)
|
||||
|
||||
// Get video sources
|
||||
resp, body := apiGet(t, "/assets/video-sources")
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("GET video-sources: %d", resp.StatusCode)
|
||||
}
|
||||
page := string(body)
|
||||
|
||||
// Find a video source
|
||||
sourceName := ""
|
||||
for _, line := range strings.Split(page, "\n") {
|
||||
for _, prefix := range []string{">cam", ">src_", ">gate_"} {
|
||||
if idx := strings.Index(line, prefix); idx >= 0 {
|
||||
rest := line[idx+1:]
|
||||
if end := strings.Index(rest, "<"); end > 1 {
|
||||
sourceName = rest[:end]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if sourceName != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
if sourceName == "" {
|
||||
t.Skip("no video source found")
|
||||
}
|
||||
t.Logf("video source: %s", sourceName)
|
||||
|
||||
// Deploy via wizard
|
||||
req := map[string]any{
|
||||
"device_id": deviceID,
|
||||
"features": []string{"face"},
|
||||
"source_names": []string{sourceName},
|
||||
}
|
||||
resp, body = apiPost(t, "/wizard/apply", req)
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("wizard apply: %d — %s", resp.StatusCode, string(body))
|
||||
}
|
||||
var result AutoConfigResult
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
t.Fatalf("parse result: %v", err)
|
||||
}
|
||||
if result.Error != "" {
|
||||
t.Fatalf("wizard error: %s", result.Error)
|
||||
}
|
||||
t.Logf("wizard OK: template=%s task=%s", result.TemplateName, result.TaskID)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user