package api_test import ( "net/http" "testing" ) // TestConfigRuleCRUD exercises the full config-rule REST surface: list, create, // get, update, versioning (list/get/promote/fork), check, preview, and delete, // plus the principal error paths. func TestConfigRuleCRUD(t *testing.T) { srv, teardown := setup(t) defer teardown() // A set the rule (and preview) can bind to. resp := postJSON(t, srv, "/api/v1/config/sets", map[string]any{ "name": "S", "parameters": []map[string]any{ {"key": "voltage", "ds": "stub", "signal": "setpoint", "type": "float64", "default": 12.0}, }, }) assertStatus(t, resp, http.StatusCreated) var set struct { ID string `json:"id"` } readJSON(t, resp, &set) // Empty list initially. resp = get(t, srv, "/api/v1/config/rules") assertStatus(t, resp, http.StatusOK) var list []map[string]any readJSON(t, resp, &list) if len(list) != 0 { t.Fatalf("expected no rules, got %d", len(list)) } // Create. resp = postJSON(t, srv, "/api/v1/config/rules", map[string]any{ "name": "cap", "setId": set.ID, "source": "voltage: <=24", }) assertStatus(t, resp, http.StatusCreated) var rule struct { ID string `json:"id"` Version int `json:"version"` } readJSON(t, resp, &rule) if rule.ID == "" { t.Fatal("rule missing id") } // Create with invalid CUE → 400. resp = postJSON(t, srv, "/api/v1/config/rules", map[string]any{ "name": "bad", "setId": set.ID, "source": "voltage: <=", }) assertStatus(t, resp, http.StatusBadRequest) // Create with malformed JSON → 400. resp = postRaw(t, srv, "/api/v1/config/rules", "application/json", []byte(`{not json`)) assertStatus(t, resp, http.StatusBadRequest) // Get. resp = get(t, srv, "/api/v1/config/rules/"+rule.ID) assertStatus(t, resp, http.StatusOK) resp.Body.Close() // Get missing → 404. resp = get(t, srv, "/api/v1/config/rules/nope") assertStatus(t, resp, http.StatusNotFound) // Update (bumps version, creates backup). resp = putRaw(t, srv, "/api/v1/config/rules/"+rule.ID, "application/json", []byte(`{"name":"cap2","setId":"`+set.ID+`","source":"voltage: <=30"}`)) assertStatus(t, resp, http.StatusOK) // List versions. resp = get(t, srv, "/api/v1/config/rules/"+rule.ID+"/versions") assertStatus(t, resp, http.StatusOK) var versions []map[string]any readJSON(t, resp, &versions) if len(versions) < 2 { t.Fatalf("expected >=2 rule versions, got %d", len(versions)) } // Get v1. resp = get(t, srv, "/api/v1/config/rules/"+rule.ID+"/versions/1") assertStatus(t, resp, http.StatusOK) resp.Body.Close() // Bad version path → 400. resp = get(t, srv, "/api/v1/config/rules/"+rule.ID+"/versions/xyz") assertStatus(t, resp, http.StatusBadRequest) // Promote v1. resp = postRaw(t, srv, "/api/v1/config/rules/"+rule.ID+"/versions/1/promote", "application/json", nil) assertStatus(t, resp, http.StatusOK) // Fork v1 → new id. resp = postRaw(t, srv, "/api/v1/config/rules/"+rule.ID+"/versions/1/fork", "application/json", nil) assertStatus(t, resp, http.StatusCreated) var fork struct { ID string `json:"id"` } readJSON(t, resp, &fork) if fork.ID == "" || fork.ID == rule.ID { t.Errorf("fork id = %q, want fresh id", fork.ID) } // Check (live validation of an unsaved source against sample values). resp = postJSON(t, srv, "/api/v1/config/rules/check", map[string]any{ "source": "voltage: <=24", "values": map[string]any{"voltage": 20.0}, }) assertStatus(t, resp, http.StatusOK) resp.Body.Close() // Check with malformed JSON → 400. resp = postRaw(t, srv, "/api/v1/config/rules/check", "application/json", []byte(`{`)) assertStatus(t, resp, http.StatusBadRequest) // Preview against the bound set's live signals. resp = postJSON(t, srv, "/api/v1/config/rules/preview", map[string]any{ "setId": set.ID, "source": "voltage: <=24", }) assertStatus(t, resp, http.StatusOK) resp.Body.Close() // Preview with an unknown set → 404. resp = postJSON(t, srv, "/api/v1/config/rules/preview", map[string]any{ "setId": "does-not-exist", "source": "voltage: <=24", }) assertStatus(t, resp, http.StatusNotFound) // Delete. resp = deleteReq(t, srv, "/api/v1/config/rules/"+rule.ID) assertStatus(t, resp, http.StatusNoContent) // Delete missing → 404. resp = deleteReq(t, srv, "/api/v1/config/rules/"+rule.ID) assertStatus(t, resp, http.StatusNotFound) }