package api_test import ( "net/http" "testing" ) // TestConfigSetCRUD exercises create → get → version → apply over HTTP, using // the stub data source's writable "setpoint" signal as the apply target. func TestConfigManagerEndToEnd(t *testing.T) { srv, teardown := setup(t) defer teardown() // Create a config set targeting the stub's writable setpoint. setBody := map[string]any{ "name": "Stub PSU", "parameters": []map[string]any{ {"key": "sp", "ds": "stub", "signal": "setpoint", "type": "float64", "default": 42.0, "mandatory": true, "min": 0.0, "max": 100.0}, }, } resp := postJSON(t, srv, "/api/v1/config/sets", setBody) assertStatus(t, resp, http.StatusCreated) var set struct { ID string `json:"id"` Version int `json:"version"` } readJSON(t, resp, &set) if set.ID == "" || set.Version != 1 { t.Fatalf("unexpected created set: %+v", set) } // List sets includes it. resp = get(t, srv, "/api/v1/config/sets") assertStatus(t, resp, http.StatusOK) var sets []map[string]any readJSON(t, resp, &sets) if len(sets) != 1 { t.Fatalf("want 1 set, got %d", len(sets)) } // Create an instance assigning a concrete value. instBody := map[string]any{ "name": "nominal", "setId": set.ID, "values": map[string]any{"sp": 73.0}, } resp = postJSON(t, srv, "/api/v1/config/instances", instBody) assertStatus(t, resp, http.StatusCreated) var inst struct { ID string `json:"id"` } readJSON(t, resp, &inst) if inst.ID == "" { t.Fatal("instance missing id") } // Apply writes the value to the target signal. resp = postJSON(t, srv, "/api/v1/config/instances/"+inst.ID+"/apply", nil) assertStatus(t, resp, http.StatusOK) var res struct { Applied int `json:"applied"` Failed int `json:"failed"` Entries []struct { Signal string `json:"signal"` OK bool `json:"ok"` } `json:"entries"` } readJSON(t, resp, &res) if res.Applied != 1 || res.Failed != 0 { t.Fatalf("apply summary: applied=%d failed=%d", res.Applied, res.Failed) } if len(res.Entries) != 1 || res.Entries[0].Signal != "setpoint" || !res.Entries[0].OK { t.Errorf("unexpected apply entries: %+v", res.Entries) } // Delete the set soft-deletes it. resp = deleteReq(t, srv, "/api/v1/config/sets/"+set.ID) assertStatus(t, resp, http.StatusNoContent) resp = get(t, srv, "/api/v1/config/sets/"+set.ID) assertStatus(t, resp, http.StatusNotFound) } // TestConfigInstanceRejectsInvalidValue verifies server-side validation against // the bound set (value above the parameter's max). func TestConfigInstanceRejectsInvalidValue(t *testing.T) { srv, teardown := setup(t) defer teardown() resp := postJSON(t, srv, "/api/v1/config/sets", map[string]any{ "name": "S", "parameters": []map[string]any{ {"key": "sp", "ds": "stub", "signal": "setpoint", "type": "float64", "max": 10.0}, }, }) assertStatus(t, resp, http.StatusCreated) var set struct { ID string `json:"id"` } readJSON(t, resp, &set) resp = postJSON(t, srv, "/api/v1/config/instances", map[string]any{ "name": "bad", "setId": set.ID, "values": map[string]any{"sp": 999.0}, }) assertStatus(t, resp, http.StatusBadRequest) }