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) } }