Completes the naming separation: nftables table, rules file, dnsmasq config, resolved config, systemd unit, sudoers rule, and temp files now all use the wild-central name.
131 lines
3.9 KiB
Go
131 lines
3.9 KiB
Go
package v1
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func setupTestNftables(t *testing.T) (*API, string) {
|
|
tmpDir := t.TempDir()
|
|
|
|
t.Setenv("WILD_CENTRAL_HAPROXY_CONFIG_PATH", filepath.Join(tmpDir, "haproxy.cfg"))
|
|
t.Setenv("WILD_CENTRAL_NFTABLES_RULES_PATH", filepath.Join(tmpDir, "wild-central.nft"))
|
|
|
|
api, err := NewAPI(tmpDir, "", nil)
|
|
if err != nil {
|
|
t.Fatalf("failed to create test API: %v", err)
|
|
}
|
|
return api, tmpDir
|
|
}
|
|
|
|
// TestNftablesStatus_ReturnsOK verifies the status endpoint returns 200.
|
|
// GetStatus() runs `nft list table inet wild-central` and silently returns an
|
|
// empty string if nft is not installed or the table doesn't exist, so this
|
|
// endpoint always returns 200.
|
|
func TestNftablesStatus_ReturnsOK(t *testing.T) {
|
|
api, tmpDir := setupTestNftables(t)
|
|
|
|
req := httptest.NewRequest("GET", "/api/v1/nftables/status", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
api.NftablesStatus(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var resp map[string]any
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("invalid JSON: %v", err)
|
|
}
|
|
|
|
if _, ok := resp["rulesFile"]; !ok {
|
|
t.Error("expected 'rulesFile' field in response")
|
|
}
|
|
if _, ok := resp["rules"]; !ok {
|
|
t.Error("expected 'rules' field in response")
|
|
}
|
|
|
|
rulesFile, _ := resp["rulesFile"].(string)
|
|
expected := filepath.Join(tmpDir, "wild-central.nft")
|
|
if rulesFile != expected {
|
|
t.Errorf("expected rulesFile=%q, got %q", expected, rulesFile)
|
|
}
|
|
}
|
|
|
|
// TestNftablesGetInterfaces_ReturnsOK verifies the interfaces endpoint returns 200
|
|
// with a valid interfaces list. Uses the real net.Interfaces() so we just check
|
|
// structure — the test host will always have at least one interface (lo is excluded,
|
|
// but any other up interface counts; if only loopback exists the list can be empty).
|
|
func TestNftablesGetInterfaces_ReturnsOK(t *testing.T) {
|
|
api, _ := setupTestNftables(t)
|
|
|
|
req := httptest.NewRequest("GET", "/api/v1/nftables/interfaces", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
api.NftablesGetInterfaces(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var resp map[string]any
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("invalid JSON: %v", err)
|
|
}
|
|
|
|
ifaces, ok := resp["interfaces"]
|
|
if !ok {
|
|
t.Fatal("expected 'interfaces' field in response")
|
|
}
|
|
|
|
// Each entry must have a name and addresses array
|
|
for _, entry := range ifaces.([]any) {
|
|
iface := entry.(map[string]any)
|
|
name, hasName := iface["name"].(string)
|
|
if !hasName || name == "" {
|
|
t.Errorf("interface entry missing non-empty name: %v", iface)
|
|
}
|
|
if _, hasAddrs := iface["addresses"]; !hasAddrs {
|
|
t.Errorf("interface %q missing addresses field", name)
|
|
}
|
|
// Loopback must not appear
|
|
if name == "lo" {
|
|
t.Errorf("loopback interface 'lo' should be excluded")
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestNftablesApply_ServiceUnavailable verifies the handler returns 500 when
|
|
// the nftables reload service is not available (expected in test environments).
|
|
func TestNftablesApply_ServiceUnavailable(t *testing.T) {
|
|
api, _ := setupTestNftables(t)
|
|
|
|
req := httptest.NewRequest("POST", "/api/v1/nftables/apply", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
api.NftablesApply(w, req)
|
|
|
|
// ApplyRules calls `systemctl start wild-central-nftables-reload.service`
|
|
// which requires polkit/root — always fails in test environments.
|
|
if w.Code != http.StatusInternalServerError {
|
|
// If it happened to succeed (running on the actual Wild Central device
|
|
// with the right polkit rules), that's also valid.
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("unexpected status %d: %s", w.Code, w.Body.String())
|
|
}
|
|
return
|
|
}
|
|
|
|
var resp map[string]any
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("invalid JSON: %v", err)
|
|
}
|
|
if resp["error"] == nil {
|
|
t.Error("expected 'error' field in 500 response")
|
|
}
|
|
}
|