dhcpsvc: add tests

This commit is contained in:
Eugene Burkov
2025-12-10 17:20:29 +03:00
parent 6dbc4b84a7
commit a0cb9f22d0
6 changed files with 228 additions and 90 deletions

View File

@@ -121,7 +121,7 @@ func TestConfig_Validate(t *testing.T) {
}, {
conf: &dhcpsvc.Config{
Enabled: true,
Logger: discardLog,
Logger: testLogger,
LocalDomainName: testLocalTLD,
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
"eth0": {
@@ -136,7 +136,7 @@ func TestConfig_Validate(t *testing.T) {
}, {
conf: &dhcpsvc.Config{
Enabled: true,
Logger: discardLog,
Logger: testLogger,
LocalDomainName: testLocalTLD,
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
"eth0": {
@@ -151,7 +151,7 @@ func TestConfig_Validate(t *testing.T) {
}, {
conf: &dhcpsvc.Config{
Enabled: true,
Logger: discardLog,
Logger: testLogger,
LocalDomainName: testLocalTLD,
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
"eth0": {
@@ -167,7 +167,7 @@ func TestConfig_Validate(t *testing.T) {
}, {
conf: &dhcpsvc.Config{
Enabled: true,
Logger: discardLog,
Logger: testLogger,
LocalDomainName: testLocalTLD,
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
"eth0": {

View File

@@ -1,12 +1,20 @@
package dhcpsvc_test
import (
"cmp"
"io/fs"
"net/netip"
"os"
"path"
"path/filepath"
"testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/stretchr/testify/require"
)
// testLocalTLD is a common local TLD for tests.
@@ -15,8 +23,11 @@ const testLocalTLD = "local"
// testTimeout is a common timeout for tests and contexts.
const testTimeout time.Duration = 10 * time.Second
// discardLog is a logger to discard test output.
var discardLog = slogutil.NewDiscardLogger()
// testLogger is a common logger for tests.
var testLogger = slogutil.NewDiscardLogger()
// testdata is a filesystem containing data for tests.
var testdata = os.DirFS("testdata")
// testInterfaceConf is a common set of interface configurations for tests.
var testInterfaceConf = map[string]*dhcpsvc.InterfaceConfig{
@@ -57,3 +68,50 @@ var testInterfaceConf = map[string]*dhcpsvc.InterfaceConfig{
},
},
}
// newTempDB copies the leases database file located in the testdata FS, under
// tb.Name()/leases.json, to a temporary directory and returns the path to the
// copied file.
func newTempDB(tb testing.TB) (dst string) {
tb.Helper()
const filename = "leases.json"
data, err := fs.ReadFile(testdata, path.Join(tb.Name(), filename))
require.NoError(tb, err)
dst = filepath.Join(tb.TempDir(), filename)
err = os.WriteFile(dst, data, dhcpsvc.DatabasePerm)
require.NoError(tb, err)
return dst
}
// newTestDHCPServer creates a new DHCPServer for testing. It uses the default
// values of config in case it's nil or some of its fields aren't set.
func newTestDHCPServer(t *testing.T, conf *dhcpsvc.Config) (srv *dhcpsvc.DHCPServer) {
t.Helper()
conf = cmp.Or(conf, &dhcpsvc.Config{
Enabled: true,
})
if conf.Interfaces == nil {
conf.Interfaces = testInterfaceConf
}
conf.NetworkDeviceManager = cmp.Or[dhcpsvc.NetworkDeviceManager](
conf.NetworkDeviceManager,
dhcpsvc.EmptyNetworkDeviceManager{},
)
conf.Logger = cmp.Or(conf.Logger, testLogger)
conf.LocalDomainName = cmp.Or(conf.LocalDomainName, testLocalTLD)
if conf.DBFilePath == "" {
conf.DBFilePath = filepath.Join(t.TempDir(), "leases.json")
}
conf.ICMPTimeout = cmp.Or(conf.ICMPTimeout, testTimeout)
srv, err := dhcpsvc.New(testutil.ContextWithTimeout(t, testTimeout), conf)
require.NoError(t, err)
return srv
}

View File

@@ -0,0 +1,41 @@
package dhcpsvc_test
import (
"context"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/golibs/testutil/servicetest"
)
func TestDHCPServer_ServeEther4(t *testing.T) {
t.Parallel()
ndMgr := &testNetworkDeviceManager{
onOpen: func(
ctx context.Context,
conf *dhcpsvc.NetworkDeviceConfig,
) (nd dhcpsvc.NetworkDevice, err error) {
return &testNetworkDevice{
// TODO(e.burkov): !! implement ReadPacketData, WritePacketData, and LinkType
}, nil
},
}
srv := newTestDHCPServer(t, &dhcpsvc.Config{
NetworkDeviceManager: ndMgr,
Enabled: true,
})
servicetest.RequireRun(t, srv, testTimeout)
testCases := []struct {
name string
// TODO(e.burkov): !! define other fields.
}{}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// TODO(e.burkov): !! implement a test
})
}
}

View File

@@ -34,6 +34,22 @@ type NetworkDeviceManager interface {
Open(ctx context.Context, conf *NetworkDeviceConfig) (dev NetworkDevice, err error)
}
// EmptyNetworkDeviceManager is an empty implementation of
// [NetworkDeviceManager].
type EmptyNetworkDeviceManager struct{}
// type check
var _ NetworkDeviceManager = EmptyNetworkDeviceManager{}
// Open implements the [NetworkDeviceManager] interface for
// [EmptyNetworkDeviceManager]. It always returns [EmptyNetworkDevice].
func (EmptyNetworkDeviceManager) Open(
_ context.Context,
_ *NetworkDeviceConfig,
) (nd NetworkDevice, err error) {
return nil, nil
}
// NetworkDevice provides reading and writing packets to a network interface.
type NetworkDevice interface {
gopacket.PacketDataSource
@@ -45,6 +61,31 @@ type NetworkDevice interface {
WritePacketData(data []byte) (err error)
}
// EmptyNetworkDevice is an empty implementation of NetworkDevice.
type EmptyNetworkDevice struct{}
// type check
var _ NetworkDevice = EmptyNetworkDevice{}
// ReadPacketData implements the [gopacket.PacketDataSource] interface for
// [EmptyNetworkDevice]. It always returns no data, empty capture info and a
// nil error.
func (EmptyNetworkDevice) ReadPacketData() (data []byte, ci gopacket.CaptureInfo, err error) {
return nil, gopacket.CaptureInfo{}, nil
}
// LinkType implements the [NetworkDevice] interface for [EmptyNetworkDevice].
// It always returns [layers.LinkTypeNull].
func (EmptyNetworkDevice) LinkType() (lt layers.LinkType) {
return layers.LinkTypeNull
}
// WritePacketData implements the [NetworkDevice] interface for
// [EmptyNetworkDevice]. It always returns nil.
func (EmptyNetworkDevice) WritePacketData(_ []byte) (err error) {
return nil
}
// frameData stores the Ethernet and IPv4 layers of the incoming packet, and
// the network device that the packet was received from.
type frameData struct {

View File

@@ -0,0 +1,57 @@
package dhcpsvc_test
import (
"context"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)
// testNetworkDeviceManager is a mock implementation of the
// [dhcpsvc.NetworkDeviceManager] interface.
//
// TODO(e.burkov): Move to aghtest.
type testNetworkDeviceManager struct {
onOpen func(
ctx context.Context,
conf *dhcpsvc.NetworkDeviceConfig,
) (nd dhcpsvc.NetworkDevice, err error)
}
// Open implements the [NetworkDeviceManager] interface for
// *testNetworkDeviceManager.
func (ndm *testNetworkDeviceManager) Open(
ctx context.Context,
conf *dhcpsvc.NetworkDeviceConfig,
) (dev dhcpsvc.NetworkDevice, err error) {
return ndm.onOpen(ctx, conf)
}
// testNetworkDevice is a mock implementation of the [dhcpsvc.NetworkDevice]
// interface.
//
// TODO(e.burkov): Move to aghtest.
type testNetworkDevice struct {
onReadPacketData func() (data []byte, ci gopacket.CaptureInfo, err error)
onLinkType func() (lt layers.LinkType)
onWritePacketData func(data []byte) (err error)
}
// ReadPacketData implements the [dhcpsvc.NetworkDevice] interface for
// *testNetworkDevice.
func (nd *testNetworkDevice) ReadPacketData() (data []byte, ci gopacket.CaptureInfo, err error) {
return nd.onReadPacketData()
}
// WritePacketData implements the [dhcpsvc.NetworkDevice] interface for
// *testNetworkDevice.
func (nd *testNetworkDevice) WritePacketData(data []byte) (err error) {
return nd.onWritePacketData(data)
}
// LinkType implements the [dhcpsvc.NetworkDevice] interface for
// *testNetworkDevice.
func (nd *testNetworkDevice) LinkType() (lt layers.LinkType) {
return nd.onLinkType()
}

View File

@@ -1,11 +1,8 @@
package dhcpsvc_test
import (
"io/fs"
"net"
"net/netip"
"os"
"path"
"path/filepath"
"strings"
"testing"
@@ -18,40 +15,12 @@ import (
"github.com/stretchr/testify/require"
)
// testdata is a filesystem containing data for tests.
var testdata = os.DirFS("testdata")
// newTempDB copies the leases database file located in the testdata FS, under
// tb.Name()/leases.json, to a temporary directory and returns the path to the
// copied file.
func newTempDB(tb testing.TB) (dst string) {
tb.Helper()
const filename = "leases.json"
data, err := fs.ReadFile(testdata, path.Join(tb.Name(), filename))
require.NoError(tb, err)
dst = filepath.Join(tb.TempDir(), filename)
err = os.WriteFile(dst, data, dhcpsvc.DatabasePerm)
require.NoError(tb, err)
return dst
}
func TestDHCPServer_AddLease(t *testing.T) {
ctx := testutil.ContextWithTimeout(t, testTimeout)
leasesPath := filepath.Join(t.TempDir(), "leases.json")
srv, err := dhcpsvc.New(ctx, &dhcpsvc.Config{
Enabled: true,
Logger: discardLog,
LocalDomainName: testLocalTLD,
Interfaces: testInterfaceConf,
DBFilePath: leasesPath,
srv := newTestDHCPServer(t, &dhcpsvc.Config{
DBFilePath: leasesPath,
Enabled: true,
})
require.NoError(t, err)
const (
existHost = "host1"
@@ -69,6 +38,7 @@ func TestDHCPServer_AddLease(t *testing.T) {
ipv6MAC = errors.Must(net.ParseMAC("02:03:04:05:06:07"))
)
ctx := testutil.ContextWithTimeout(t, testTimeout)
require.NoError(t, srv.AddLease(ctx, &dhcpsvc.Lease{
Hostname: existHost,
IP: existIP,
@@ -144,6 +114,7 @@ func TestDHCPServer_AddLease(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx = testutil.ContextWithTimeout(t, testTimeout)
testutil.AssertErrorMsg(t, tc.wantErrMsg, srv.AddLease(ctx, tc.lease))
})
}
@@ -153,17 +124,11 @@ func TestDHCPServer_AddLease(t *testing.T) {
}
func TestDHCPServer_index(t *testing.T) {
ctx := testutil.ContextWithTimeout(t, testTimeout)
leasesPath := newTempDB(t)
srv, err := dhcpsvc.New(ctx, &dhcpsvc.Config{
Enabled: true,
Logger: discardLog,
LocalDomainName: testLocalTLD,
Interfaces: testInterfaceConf,
DBFilePath: leasesPath,
srv := newTestDHCPServer(t, &dhcpsvc.Config{
DBFilePath: leasesPath,
Enabled: true,
})
require.NoError(t, err)
const (
host1 = "host1"
@@ -210,17 +175,11 @@ func TestDHCPServer_index(t *testing.T) {
}
func TestDHCPServer_UpdateStaticLease(t *testing.T) {
ctx := testutil.ContextWithTimeout(t, testTimeout)
leasesPath := newTempDB(t)
srv, err := dhcpsvc.New(ctx, &dhcpsvc.Config{
Enabled: true,
Logger: discardLog,
LocalDomainName: testLocalTLD,
Interfaces: testInterfaceConf,
DBFilePath: leasesPath,
srv := newTestDHCPServer(t, &dhcpsvc.Config{
DBFilePath: leasesPath,
Enabled: true,
})
require.NoError(t, err)
const (
host1 = "host1"
@@ -309,6 +268,7 @@ func TestDHCPServer_UpdateStaticLease(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx := testutil.ContextWithTimeout(t, testTimeout)
testutil.AssertErrorMsg(t, tc.wantErrMsg, srv.UpdateStaticLease(ctx, tc.lease))
})
}
@@ -317,17 +277,11 @@ func TestDHCPServer_UpdateStaticLease(t *testing.T) {
}
func TestDHCPServer_RemoveLease(t *testing.T) {
ctx := testutil.ContextWithTimeout(t, testTimeout)
leasesPath := newTempDB(t)
srv, err := dhcpsvc.New(ctx, &dhcpsvc.Config{
Enabled: true,
Logger: discardLog,
LocalDomainName: testLocalTLD,
Interfaces: testInterfaceConf,
DBFilePath: leasesPath,
srv := newTestDHCPServer(t, &dhcpsvc.Config{
DBFilePath: leasesPath,
Enabled: true,
})
require.NoError(t, err)
const (
host1 = "host1"
@@ -393,6 +347,7 @@ func TestDHCPServer_RemoveLease(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx := testutil.ContextWithTimeout(t, testTimeout)
testutil.AssertErrorMsg(t, tc.wantErrMsg, srv.RemoveLease(ctx, tc.lease))
})
}
@@ -403,22 +358,16 @@ func TestDHCPServer_RemoveLease(t *testing.T) {
func TestDHCPServer_Reset(t *testing.T) {
leasesPath := newTempDB(t)
conf := &dhcpsvc.Config{
Enabled: true,
Logger: discardLog,
LocalDomainName: testLocalTLD,
Interfaces: testInterfaceConf,
DBFilePath: leasesPath,
}
ctx := testutil.ContextWithTimeout(t, testTimeout)
srv, err := dhcpsvc.New(ctx, conf)
require.NoError(t, err)
srv := newTestDHCPServer(t, &dhcpsvc.Config{
DBFilePath: leasesPath,
Enabled: true,
})
const leasesNum = 4
require.Len(t, srv.Leases(), leasesNum)
ctx := testutil.ContextWithTimeout(t, testTimeout)
require.NoError(t, srv.Reset(ctx))
assert.FileExists(t, leasesPath)
@@ -427,18 +376,10 @@ func TestDHCPServer_Reset(t *testing.T) {
func TestServer_Leases(t *testing.T) {
leasesPath := newTempDB(t)
conf := &dhcpsvc.Config{
Enabled: true,
Logger: discardLog,
LocalDomainName: testLocalTLD,
Interfaces: testInterfaceConf,
DBFilePath: leasesPath,
}
ctx := testutil.ContextWithTimeout(t, testTimeout)
srv, err := dhcpsvc.New(ctx, conf)
require.NoError(t, err)
srv := newTestDHCPServer(t, &dhcpsvc.Config{
DBFilePath: leasesPath,
Enabled: true,
})
expiry, err := time.Parse(time.RFC3339, "2042-01-02T03:04:05Z")
require.NoError(t, err)