仓库结构: edge/ 设备端(原根目录设备端代码整体移入) control/ 管理端(清理后) docs/ 文档(PRD 移入 design/) README.md 根导航(新增) 清理: - control/.brainstorm 临时草稿删除 - control 根级重复文档(API表/PRD_04)并入 docs/design/ - control/plan.md -> docs/implementation/control-plan.md - control/safesightd-linux-arm64 二进制取消版本控制(.gitignore) - edge/transform 模型转换产物归入 models/,onnx/pt 大源文件取消跟踪(.gitignore) - Readme.md(PRD) -> docs/design/PRD_Product_v1.2.md(避开 README 大小写冲突) 更新: - 根 README.md 导航、docs/README.md 文档索引 - deployment.md/检查表路径加 edge/ 前缀 - .gitignore 重写(edge/control 分区规则)
95 lines
3.0 KiB
Go
95 lines
3.0 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"safesight-agent/internal/config"
|
|
)
|
|
|
|
func TestHandleConfigCandidateStoresValidatedCandidate(t *testing.T) {
|
|
dir := t.TempDir()
|
|
cfgPath := filepath.Join(dir, "media-server.json")
|
|
candidatePath := cfgPath + ".candidate.json"
|
|
body := []byte(`{"templates":{"tpl":{"nodes":[],"edges":[]}},"instances":[],"metadata":{"config_id":"cfg-preview","config_version":"v2"}}`)
|
|
|
|
s := &Server{
|
|
agentCfg: config.AgentConfig{ConfigPath: cfgPath, MaxUploadMB: 1, Token: "test-token"},
|
|
}
|
|
req := httptest.NewRequest(http.MethodPut, "/v1/config/candidate", strings.NewReader(string(body)))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("X-RK-Token", "test-token")
|
|
rr := httptest.NewRecorder()
|
|
|
|
s.handleConfigCandidate(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("status code: got %d body=%s", rr.Code, rr.Body.String())
|
|
}
|
|
written, err := os.ReadFile(candidatePath)
|
|
if err != nil {
|
|
t.Fatalf("read candidate: %v", err)
|
|
}
|
|
if strings.TrimSpace(string(written)) != string(body) {
|
|
t.Fatalf("candidate body = %s", written)
|
|
}
|
|
|
|
var got map[string]any
|
|
if err := json.Unmarshal(rr.Body.Bytes(), &got); err != nil {
|
|
t.Fatalf("decode response: %v", err)
|
|
}
|
|
if got["path"] != filepath.ToSlash(candidatePath) {
|
|
t.Fatalf("path = %v", got["path"])
|
|
}
|
|
sum := sha256.Sum256(written)
|
|
if got["sha256"] != hex.EncodeToString(sum[:]) {
|
|
t.Fatalf("sha256 = %v", got["sha256"])
|
|
}
|
|
metadata, ok := got["metadata"].(map[string]any)
|
|
if !ok || metadata["config_id"] != "cfg-preview" || metadata["config_version"] != "v2" {
|
|
t.Fatalf("metadata = %#v", got["metadata"])
|
|
}
|
|
|
|
statusReq := httptest.NewRequest(http.MethodGet, "/v1/config/status", nil)
|
|
statusRR := httptest.NewRecorder()
|
|
s.handleConfigStatus(statusRR, statusReq)
|
|
if statusRR.Code != http.StatusOK {
|
|
t.Fatalf("status response code: %d body=%s", statusRR.Code, statusRR.Body.String())
|
|
}
|
|
var status map[string]any
|
|
if err := json.Unmarshal(statusRR.Body.Bytes(), &status); err != nil {
|
|
t.Fatalf("decode status: %v", err)
|
|
}
|
|
candidate, ok := status["candidate"].(map[string]any)
|
|
if !ok || candidate["exists"] != true {
|
|
t.Fatalf("candidate status = %#v", status["candidate"])
|
|
}
|
|
}
|
|
|
|
func TestHandleConfigCandidateRejectsInvalidJSON(t *testing.T) {
|
|
dir := t.TempDir()
|
|
cfgPath := filepath.Join(dir, "media-server.json")
|
|
s := &Server{agentCfg: config.AgentConfig{ConfigPath: cfgPath, MaxUploadMB: 1, Token: "test-token"}}
|
|
|
|
req := httptest.NewRequest(http.MethodPut, "/v1/config/candidate", strings.NewReader(`{"instances":[]`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("X-RK-Token", "test-token")
|
|
rr := httptest.NewRecorder()
|
|
|
|
s.handleConfigCandidate(rr, req)
|
|
|
|
if rr.Code != http.StatusBadRequest {
|
|
t.Fatalf("status code: got %d body=%s", rr.Code, rr.Body.String())
|
|
}
|
|
if _, err := os.Stat(cfgPath + ".candidate.json"); !os.IsNotExist(err) {
|
|
t.Fatalf("candidate file should not exist, stat err=%v", err)
|
|
}
|
|
}
|