OrangePi3588Media/agent/internal/httpapi/config_status_test.go

173 lines
5.8 KiB
Go

package httpapi
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"rk3588sys/agent/internal/config"
"rk3588sys/agent/internal/procctl"
)
type fakeProcessController struct {
status procctl.Status
}
func (f fakeProcessController) Enabled() bool { return true }
func (f fakeProcessController) Status() (procctl.Status, error) {
return f.status, nil
}
func (f fakeProcessController) Version() (string, error) { return "", procctl.ErrNotSupported }
func (f fakeProcessController) Start(string) (procctl.Status, error) {
return procctl.Status{}, procctl.ErrNotSupported
}
func (f fakeProcessController) Stop() (procctl.Status, error) {
return procctl.Status{}, procctl.ErrNotSupported
}
func (f fakeProcessController) Restart(string) (procctl.Status, error) {
return procctl.Status{}, procctl.ErrNotSupported
}
func (f fakeProcessController) BinaryInfo() (procctl.BinaryUpdateResult, error) {
return procctl.BinaryUpdateResult{}, procctl.ErrNotSupported
}
func (f fakeProcessController) UpdateBinary(io.Reader, int64, string) (procctl.BinaryUpdateResult, error) {
return procctl.BinaryUpdateResult{}, procctl.ErrNotSupported
}
func (f fakeProcessController) RollbackBinary(string) (procctl.BinaryUpdateResult, error) {
return procctl.BinaryUpdateResult{}, procctl.ErrNotSupported
}
func TestHandleConfigStatusReportsMetadataHashAndMediaStatus(t *testing.T) {
dir := t.TempDir()
cfgPath := filepath.Join(dir, "config.json")
body := []byte(`{"metadata":{"config_id":"cfg-1","config_version":"v1","template":"workshop_face_shoe_alarm","profile":"local_3588_test","overlays":["face_debug"],"instance_names":["cam1"],"instance_display_names":["视觉识别终端-A厂区"]},"instances":[]}`)
if err := os.WriteFile(cfgPath, body, 0o644); err != nil {
t.Fatalf("write config: %v", err)
}
if err := os.WriteFile(cfgPath+".last_good.json", []byte(`{"metadata":{"config_id":"cfg-0"}}`), 0o644); err != nil {
t.Fatalf("write last good: %v", err)
}
s := &Server{
agentCfg: config.AgentConfig{ConfigPath: cfgPath},
proc: fakeProcessController{status: procctl.Status{
Running: true,
Pid: 1234,
ConfigPath: cfgPath,
StartedAtMS: 1000,
}},
}
req := httptest.NewRequest(http.MethodGet, "/v1/config/status", nil)
rr := httptest.NewRecorder()
s.handleConfigStatus(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status code: got %d body=%s", rr.Code, rr.Body.String())
}
var got map[string]any
if err := json.Unmarshal(rr.Body.Bytes(), &got); err != nil {
t.Fatalf("decode response: %v", err)
}
if got["exists"] != true {
t.Fatalf("exists = %v", got["exists"])
}
metadata, ok := got["metadata"].(map[string]any)
if !ok {
t.Fatalf("metadata missing or wrong type: %#v", got["metadata"])
}
if metadata["config_id"] != "cfg-1" || metadata["config_version"] != "v1" {
t.Fatalf("metadata = %#v", metadata)
}
if got["template"] != "workshop_face_shoe_alarm" {
t.Fatalf("template = %#v", got["template"])
}
if got["profile"] != "local_3588_test" {
t.Fatalf("profile = %#v", got["profile"])
}
if got["instance_display_name"] != "视觉识别终端-A厂区" {
t.Fatalf("instance_display_name = %#v", got["instance_display_name"])
}
overlays, ok := got["overlays"].([]any)
if !ok || len(overlays) != 1 || overlays[0] != "face_debug" {
t.Fatalf("overlays = %#v", got["overlays"])
}
sum := sha256.Sum256(body)
wantSHA := hex.EncodeToString(sum[:])
if got["sha256"] != wantSHA {
t.Fatalf("sha256 = %v want %s", got["sha256"], wantSHA)
}
media, ok := got["media_server"].(map[string]any)
if !ok || media["running"] != true || media["pid"] != float64(1234) {
t.Fatalf("media_server = %#v", got["media_server"])
}
previousConfig, ok := got["previous_config"].(map[string]any)
if !ok || previousConfig["exists"] != true {
t.Fatalf("previous_config = %#v", got["previous_config"])
}
if got["previous_config_path"] != filepath.ToSlash(cfgPath+".last_good.json") {
t.Fatalf("previous_config_path = %#v", got["previous_config_path"])
}
}
func TestHandleInfoIncludesCurrentConfigSummary(t *testing.T) {
dir := t.TempDir()
cfgPath := filepath.Join(dir, "config.json")
body := []byte(`{"metadata":{"config_id":"cfg-1","config_version":"v1","template":"workshop_face_shoe_alarm","profile":"local_3588_test","overlays":["face_debug","production_quiet"],"instance_names":["cam1"],"instance_display_names":["视觉识别终端-A厂区"]},"instances":[]}`)
if err := os.WriteFile(cfgPath, body, 0o644); err != nil {
t.Fatalf("write config: %v", err)
}
s := &Server{
agentCfg: config.AgentConfig{
ConfigPath: cfgPath,
DeviceName: "rk3588_orangepi5plus",
},
deviceID: "dev-1",
hostname: "orangepi5plus",
agentPort: 9100,
mediaPort: 9000,
version: "0.1.0",
buildID: "20260420.001",
buildType: "release",
gitSHA: "abc1234",
}
req := httptest.NewRequest(http.MethodGet, "/v1/info", nil)
rr := httptest.NewRecorder()
s.handleInfo(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status code: got %d body=%s", rr.Code, rr.Body.String())
}
var got map[string]any
if err := json.Unmarshal(rr.Body.Bytes(), &got); err != nil {
t.Fatalf("decode response: %v", err)
}
if got["config_id"] != "cfg-1" {
t.Fatalf("config_id = %#v", got["config_id"])
}
if got["config_version"] != "v1" {
t.Fatalf("config_version = %#v", got["config_version"])
}
if got["template"] != "workshop_face_shoe_alarm" {
t.Fatalf("template = %#v", got["template"])
}
if got["profile"] != "local_3588_test" {
t.Fatalf("profile = %#v", got["profile"])
}
if got["instance_display_name"] != "视觉识别终端-A厂区" {
t.Fatalf("instance_display_name = %#v", got["instance_display_name"])
}
overlays, ok := got["overlays"].([]any)
if !ok || len(overlays) != 2 {
t.Fatalf("overlays = %#v", got["overlays"])
}
}