package api import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "path/filepath" "testing" "debtik/internal/auth" "debtik/internal/vpn" ) func setupTestVXLANServer(t *testing.T) (*Server, *vpn.Manager) { tmpDir := t.TempDir() authPath := filepath.Join(tmpDir, "users.json") authMgr := auth.NewManager(authPath) vpnMgr := &vpn.Manager{ VXLAN: vpn.NewVXLANManager(filepath.Join(tmpDir, "vxlan.json")), } srv := &Server{ vpnMgr: vpnMgr, authMgr: authMgr, mux: http.NewServeMux(), fsHandler: http.NotFoundHandler(), } srv.registerRoutes() return srv, vpnMgr } func TestVXLANAPIEndpoints(t *testing.T) { srv, _ := setupTestVXLANServer(t) // 1. GET /api/vpn/vxlan (empty initially) req := httptest.NewRequest(http.MethodGet, "/api/vpn/vxlan", nil) rec := httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("GET /api/vpn/vxlan code = %d; want 200", rec.Code) } var list []*vpn.VXLANInstance if err := json.NewDecoder(rec.Body).Decode(&list); err != nil { t.Fatalf("decode failed: %v", err) } if len(list) != 0 { t.Fatalf("expected 0 vxlan tunnels, got %d", len(list)) } // 2. POST /api/vpn/vxlan (create disabled instance) payload := vpn.VXLANInstance{ ID: "vxlan-unit-1", Name: "vxlan-test", VNI: 1050, Port: 4789, Disabled: true, Comment: "API test vxlan", } b, _ := json.Marshal(payload) req = httptest.NewRequest(http.MethodPost, "/api/vpn/vxlan", bytes.NewReader(b)) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("POST /api/vpn/vxlan code = %d, body = %s; want 200", rec.Code, rec.Body.String()) } // 3. GET /api/interface/vxlan alias req = httptest.NewRequest(http.MethodGet, "/api/interface/vxlan", nil) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("GET /api/interface/vxlan code = %d; want 200", rec.Code) } list = nil _ = json.NewDecoder(rec.Body).Decode(&list) if len(list) != 1 || list[0].Name != "vxlan-test" { t.Fatalf("expected 1 vxlan named vxlan-test, got %+v", list) } // 4. POST /api/vpn/vxlan/vteps vtepPayload := vpn.VXLANVtep{ Interface: "vxlan-test", RemoteIP: "192.168.100.2", Comment: "Remote VTEP 1", } vb, _ := json.Marshal(vtepPayload) req = httptest.NewRequest(http.MethodPost, "/api/vpn/vxlan/vteps", bytes.NewReader(vb)) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("POST /api/vpn/vxlan/vteps code = %d, body = %s; want 200", rec.Code, rec.Body.String()) } // 5. GET /api/vpn/vxlan/vteps req = httptest.NewRequest(http.MethodGet, "/api/vpn/vxlan/vteps?interface=vxlan-test", nil) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("GET /api/vpn/vxlan/vteps code = %d; want 200", rec.Code) } var vteps []*vpn.VXLANVtep _ = json.NewDecoder(rec.Body).Decode(&vteps) if len(vteps) != 1 || vteps[0].RemoteIP != "192.168.100.2" { t.Fatalf("expected 1 vtep with remote-ip 192.168.100.2, got %+v", vteps) } // 6. DELETE /api/vpn/vxlan/{id}/vteps/{vtepid} req = httptest.NewRequest(http.MethodDelete, "/api/vpn/vxlan/vxlan-unit-1/vteps/192.168.100.2", nil) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("DELETE vtep code = %d, body = %s; want 200", rec.Code, rec.Body.String()) } // 7. DELETE /api/vpn/vxlan/{id} req = httptest.NewRequest(http.MethodDelete, "/api/vpn/vxlan/vxlan-unit-1", nil) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("DELETE vxlan code = %d; want 200", rec.Code) } req = httptest.NewRequest(http.MethodGet, "/api/vpn/vxlan", nil) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) list = nil _ = json.NewDecoder(rec.Body).Decode(&list) if len(list) != 0 { t.Fatalf("expected 0 vxlan tunnels after delete, got %d", len(list)) } }