AdGuardHome/internal/dhcpd/http_unix_internal_test.go

320 lines
7.0 KiB
Go

//go:build darwin || freebsd || linux || openbsd
package dhcpd
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"net/netip"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// defaultResponse is a helper that returns the response with default
// configuration.
func defaultResponse() *dhcpStatusResponse {
conf4 := defaultV4ServerConf()
conf4.LeaseDuration = 86400
resp := &dhcpStatusResponse{
V4: *conf4,
V6: V6ServerConf{},
Leases: []*leaseDynamic{},
StaticLeases: []*leaseStatic{},
Enabled: true,
}
return resp
}
// handleLease is the helper function that calls handler with provided static
// lease as body and returns modified response recorder.
func handleLease(t *testing.T, lease *leaseStatic, handler http.HandlerFunc) (w *httptest.ResponseRecorder) {
t.Helper()
w = httptest.NewRecorder()
b := &bytes.Buffer{}
err := json.NewEncoder(b).Encode(lease)
require.NoError(t, err)
var r *http.Request
r, err = http.NewRequest(http.MethodPost, "", b)
require.NoError(t, err)
handler(w, r)
return w
}
// checkStatus is a helper that asserts the response of
// [*server.handleDHCPStatus].
func checkStatus(t *testing.T, s *server, want *dhcpStatusResponse) {
w := httptest.NewRecorder()
b := &bytes.Buffer{}
err := json.NewEncoder(b).Encode(&want)
require.NoError(t, err)
r, err := http.NewRequest(http.MethodPost, "", b)
require.NoError(t, err)
s.handleDHCPStatus(w, r)
assert.Equal(t, http.StatusOK, w.Code)
assert.JSONEq(t, b.String(), w.Body.String())
}
func TestServer_handleDHCPStatus(t *testing.T) {
const (
staticName = "static-client"
staticMAC = "aa:aa:aa:aa:aa:aa"
)
staticIP := netip.MustParseAddr("192.168.10.10")
staticLease := &leaseStatic{
HWAddr: staticMAC,
IP: staticIP,
Hostname: staticName,
}
s, err := Create(&ServerConfig{
Enabled: true,
Conf4: *defaultV4ServerConf(),
DataDir: t.TempDir(),
ConfigModified: func() {},
})
require.NoError(t, err)
ok := t.Run("status", func(t *testing.T) {
resp := defaultResponse()
checkStatus(t, s, resp)
})
require.True(t, ok)
ok = t.Run("add_static_lease", func(t *testing.T) {
w := handleLease(t, staticLease, s.handleDHCPAddStaticLease)
assert.Equal(t, http.StatusOK, w.Code)
resp := defaultResponse()
resp.StaticLeases = []*leaseStatic{staticLease}
checkStatus(t, s, resp)
})
require.True(t, ok)
ok = t.Run("add_invalid_lease", func(t *testing.T) {
w := handleLease(t, staticLease, s.handleDHCPAddStaticLease)
assert.Equal(t, http.StatusBadRequest, w.Code)
})
require.True(t, ok)
ok = t.Run("remove_static_lease", func(t *testing.T) {
w := handleLease(t, staticLease, s.handleDHCPRemoveStaticLease)
assert.Equal(t, http.StatusOK, w.Code)
resp := defaultResponse()
checkStatus(t, s, resp)
})
require.True(t, ok)
ok = t.Run("set_config", func(t *testing.T) {
w := httptest.NewRecorder()
resp := defaultResponse()
resp.Enabled = false
b := &bytes.Buffer{}
err = json.NewEncoder(b).Encode(&resp)
require.NoError(t, err)
var r *http.Request
r, err = http.NewRequest(http.MethodPost, "", b)
require.NoError(t, err)
s.handleDHCPSetConfig(w, r)
assert.Equal(t, http.StatusOK, w.Code)
checkStatus(t, s, resp)
})
require.True(t, ok)
}
func TestServer_HandleUpdateStaticLease(t *testing.T) {
const (
leaseV4Name = "static-client-v4"
leaseV4MAC = "44:44:44:44:44:44"
leaseV6Name = "static-client-v6"
leaseV6MAC = "66:66:66:66:66:66"
)
leaseV4IP := netip.MustParseAddr("192.168.10.10")
leaseV6IP := netip.MustParseAddr("2001::6")
const (
leaseV4Pos = iota
leaseV6Pos
)
leases := []*leaseStatic{
leaseV4Pos: {
HWAddr: leaseV4MAC,
IP: leaseV4IP,
Hostname: leaseV4Name,
},
leaseV6Pos: {
HWAddr: leaseV6MAC,
IP: leaseV6IP,
Hostname: leaseV6Name,
},
}
s, err := Create(&ServerConfig{
Enabled: true,
Conf4: *defaultV4ServerConf(),
Conf6: V6ServerConf{},
DataDir: t.TempDir(),
ConfigModified: func() {},
})
require.NoError(t, err)
for _, l := range leases {
w := handleLease(t, l, s.handleDHCPAddStaticLease)
assert.Equal(t, http.StatusOK, w.Code)
}
testCases := []struct {
name string
pos int
lease *leaseStatic
}{{
name: "update_v4_name",
pos: leaseV4Pos,
lease: &leaseStatic{
HWAddr: leaseV4MAC,
IP: leaseV4IP,
Hostname: "updated-client-v4",
},
}, {
name: "update_v4_ip",
pos: leaseV4Pos,
lease: &leaseStatic{
HWAddr: leaseV4MAC,
IP: netip.MustParseAddr("192.168.10.200"),
Hostname: "updated-client-v4",
},
}, {
name: "update_v6_name",
pos: leaseV6Pos,
lease: &leaseStatic{
HWAddr: leaseV6MAC,
IP: leaseV6IP,
Hostname: "updated-client-v6",
},
}, {
name: "update_v6_ip",
pos: leaseV6Pos,
lease: &leaseStatic{
HWAddr: leaseV6MAC,
IP: netip.MustParseAddr("2001::666"),
Hostname: "updated-client-v6",
},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
w := handleLease(t, tc.lease, s.handleDHCPUpdateStaticLease)
assert.Equal(t, http.StatusOK, w.Code)
resp := defaultResponse()
leases[tc.pos] = tc.lease
resp.StaticLeases = leases
checkStatus(t, s, resp)
})
}
}
func TestServer_HandleUpdateStaticLease_validation(t *testing.T) {
const (
leaseV4Name = "static-client-v4"
leaseV4MAC = "44:44:44:44:44:44"
anotherV4Name = "another-client-v4"
anotherV4MAC = "55:55:55:55:55:55"
)
leaseV4IP := netip.MustParseAddr("192.168.10.10")
anotherV4IP := netip.MustParseAddr("192.168.10.20")
leases := []*leaseStatic{{
HWAddr: leaseV4MAC,
IP: leaseV4IP,
Hostname: leaseV4Name,
}, {
HWAddr: anotherV4MAC,
IP: anotherV4IP,
Hostname: anotherV4Name,
}}
s, err := Create(&ServerConfig{
Enabled: true,
Conf4: *defaultV4ServerConf(),
Conf6: V6ServerConf{},
DataDir: t.TempDir(),
ConfigModified: func() {},
})
require.NoError(t, err)
for _, l := range leases {
w := handleLease(t, l, s.handleDHCPAddStaticLease)
assert.Equal(t, http.StatusOK, w.Code)
}
testCases := []struct {
lease *leaseStatic
name string
want string
}{{
name: "v4_unknown_mac",
lease: &leaseStatic{
HWAddr: "aa:aa:aa:aa:aa:aa",
IP: leaseV4IP,
Hostname: leaseV4Name,
},
want: "dhcpv4: updating static lease: can't find lease aa:aa:aa:aa:aa:aa\n",
}, {
name: "update_v4_same_ip",
lease: &leaseStatic{
HWAddr: leaseV4MAC,
IP: anotherV4IP,
Hostname: leaseV4Name,
},
want: "dhcpv4: updating static lease: ip address is not unique\n",
}, {
name: "update_v4_same_name",
lease: &leaseStatic{
HWAddr: leaseV4MAC,
IP: leaseV4IP,
Hostname: anotherV4Name,
},
want: "dhcpv4: updating static lease: hostname is not unique\n",
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
w := handleLease(t, tc.lease, s.handleDHCPUpdateStaticLease)
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Equal(t, tc.want, w.Body.String())
})
}
}