package api import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "path/filepath" "testing" "debtik/internal/auth" "debtik/internal/sdwan" ) func setupTestSDWANServer(t *testing.T) (*Server, *sdwan.Manager) { tmpDir := t.TempDir() cfgPath := filepath.Join(tmpDir, "sdwan.json") sdwanMgr := sdwan.NewManager(cfgPath) authPath := filepath.Join(tmpDir, "users.json") authMgr := auth.NewManager(authPath) srv := &Server{ sdwanMgr: sdwanMgr, authMgr: authMgr, mux: http.NewServeMux(), fsHandler: http.NotFoundHandler(), } srv.registerRoutes() return srv, sdwanMgr } func TestSDWANAPIEndpoints(t *testing.T) { srv, sdwanMgr := setupTestSDWANServer(t) defer sdwanMgr.Stop() // 1. GET /api/sdwan/groups (Initial empty list) req := httptest.NewRequest(http.MethodGet, "/api/sdwan/groups", nil) rec := httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("GET /api/sdwan/groups status = %d; want 200", rec.Code) } var groups []*sdwan.SDWANGroup if err := json.NewDecoder(rec.Body).Decode(&groups); err != nil { t.Fatalf("failed to decode groups: %v", err) } if len(groups) != 0 { t.Fatalf("expected 0 groups, got %d", len(groups)) } // 2. POST /api/sdwan/groups (Create group) newGroup := sdwan.SDWANGroup{ Name: "sdwan0", Mode: sdwan.ModeWeighted, AutoNAT: true, ProbeInterval: 3, Comment: "Primary Multi-WAN", } body, _ := json.Marshal(newGroup) req = httptest.NewRequest(http.MethodPost, "/api/sdwan/groups", bytes.NewReader(body)) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("POST /api/sdwan/groups status = %d; body = %s", rec.Code, rec.Body.String()) } // 3. GET /api/sdwan/groups (Check group created) req = httptest.NewRequest(http.MethodGet, "/api/sdwan/groups", nil) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("GET /api/sdwan/groups status = %d", rec.Code) } groups = nil _ = json.NewDecoder(rec.Body).Decode(&groups) if len(groups) != 1 || groups[0].Name != "sdwan0" { t.Fatalf("expected 1 group named sdwan0, got %+v", groups) } groupID := groups[0].ID // 4. POST /api/sdwan/groups/{id}/members (Add member) memPayload := map[string]interface{}{ "interface": "eth0", "gateway": "192.168.1.1", "weight": 60, "priority": 1, "target_probe": "1.1.1.1", } body, _ = json.Marshal(memPayload) req = httptest.NewRequest(http.MethodPost, "/api/sdwan/groups/"+groupID+"/members", bytes.NewReader(body)) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("POST /api/sdwan/groups/%s/members status = %d; body = %s", groupID, rec.Code, rec.Body.String()) } // 4a. PUT /api/sdwan/groups/{id}/members (Update member via PUT) memPayload["weight"] = 75 body, _ = json.Marshal(memPayload) req = httptest.NewRequest(http.MethodPut, "/api/sdwan/groups/"+groupID+"/members", bytes.NewReader(body)) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("PUT /api/sdwan/groups/%s/members status = %d; body = %s", groupID, rec.Code, rec.Body.String()) } // 4b. POST /api/sdwan/groups/{id}/members (Update member via POST when already exists) memPayload["weight"] = 85 body, _ = json.Marshal(memPayload) req = httptest.NewRequest(http.MethodPost, "/api/sdwan/groups/"+groupID+"/members", bytes.NewReader(body)) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("POST (update) /api/sdwan/groups/%s/members status = %d; body = %s", groupID, rec.Code, rec.Body.String()) } gAfterUpdate, _ := sdwanMgr.Get(groupID) if len(gAfterUpdate.Members) != 1 || gAfterUpdate.Members[0].Weight != 85 { t.Fatalf("expected member weight 85, got %+v", gAfterUpdate.Members) } // 5. GET /api/sdwan/telemetry req = httptest.NewRequest(http.MethodGet, "/api/sdwan/telemetry", nil) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("GET /api/sdwan/telemetry status = %d", rec.Code) } var telemetry sdwan.TelemetrySummary if err := json.NewDecoder(rec.Body).Decode(&telemetry); err != nil { t.Fatalf("failed to decode telemetry: %v", err) } if telemetry.TotalGroups != 1 || telemetry.TotalMembers != 1 { t.Fatalf("unexpected telemetry: %+v", telemetry) } // 6. POST /api/sdwan/groups/{id}/state (Disable group) statePayload := map[string]interface{}{ "disabled": true, } body, _ = json.Marshal(statePayload) req = httptest.NewRequest(http.MethodPost, "/api/sdwan/groups/"+groupID+"/state", bytes.NewReader(body)) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("POST /api/sdwan/groups/%s/state status = %d", groupID, rec.Code) } // 7. DELETE /api/sdwan/groups/{id}/members/{iface} req = httptest.NewRequest(http.MethodDelete, "/api/sdwan/groups/"+groupID+"/members/eth0", nil) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("DELETE /api/sdwan/groups/%s/members/eth0 status = %d; body = %s", groupID, rec.Code, rec.Body.String()) } // 8. DELETE /api/sdwan/groups/{id} req = httptest.NewRequest(http.MethodDelete, "/api/sdwan/groups/"+groupID, nil) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("DELETE /api/sdwan/groups/%s status = %d; body = %s", groupID, rec.Code, rec.Body.String()) } }