|
@@ -0,0 +1,192 @@
|
|
|
|
|
+package rangeplugin
|
|
|
|
|
+
|
|
|
|
|
+import (
|
|
|
|
|
+ "fmt"
|
|
|
|
|
+ "net"
|
|
|
|
|
+ "testing"
|
|
|
|
|
+
|
|
|
|
|
+ "github.com/insomniacslk/dhcp/dhcpv4"
|
|
|
|
|
+ "github.com/stretchr/testify/assert"
|
|
|
|
|
+ "github.com/stretchr/testify/mock"
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+// mockAllocator is a simple mock for testing
|
|
|
|
|
+type mockAllocator struct {
|
|
|
|
|
+ mock.Mock
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (m *mockAllocator) Allocate(hint net.IPNet) (net.IPNet, error) {
|
|
|
|
|
+ return m.Called(hint).Get(0).(net.IPNet), nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (m *mockAllocator) Free(ip net.IPNet) error {
|
|
|
|
|
+ m.Called(ip)
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type mockFailingAllocator struct {
|
|
|
|
|
+ mock.Mock
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (m *mockFailingAllocator) Allocate(hint net.IPNet) (net.IPNet, error) {
|
|
|
|
|
+ args := m.Called(hint)
|
|
|
|
|
+ return args.Get(0).(net.IPNet), args.Error(1)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (m *mockFailingAllocator) Free(ip net.IPNet) error {
|
|
|
|
|
+ args := m.Called(ip)
|
|
|
|
|
+ return args.Error(0)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestHandler4Release(t *testing.T) {
|
|
|
|
|
+ db, dbErr := testDBSetup()
|
|
|
|
|
+ if dbErr != nil {
|
|
|
|
|
+ t.Fatalf("Failed to set up test DB: %v", dbErr)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ mockAlloc := &mockAllocator{}
|
|
|
|
|
+
|
|
|
|
|
+ pl := PluginState{
|
|
|
|
|
+ leasedb: db,
|
|
|
|
|
+ Recordsv4: make(map[string]*Record),
|
|
|
|
|
+ allocator: mockAlloc,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ loadedRecords, loadErr := loadRecords(db)
|
|
|
|
|
+ if loadErr != nil {
|
|
|
|
|
+ t.Fatalf("Failed to load records: %v", loadErr)
|
|
|
|
|
+ }
|
|
|
|
|
+ pl.Recordsv4 = loadedRecords
|
|
|
|
|
+
|
|
|
|
|
+ // Create a DHCP RELEASE request using existing test data
|
|
|
|
|
+ hwaddr, _ := net.ParseMAC(records[1].mac)
|
|
|
|
|
+ req := &dhcpv4.DHCPv4{
|
|
|
|
|
+ ClientHWAddr: hwaddr,
|
|
|
|
|
+ }
|
|
|
|
|
+ req.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeRelease))
|
|
|
|
|
+
|
|
|
|
|
+ resp := &dhcpv4.DHCPv4{}
|
|
|
|
|
+
|
|
|
|
|
+ // Verify record exists before release
|
|
|
|
|
+ record, exists := pl.Recordsv4[hwaddr.String()]
|
|
|
|
|
+ assert.True(t, exists, "Record should exist before release")
|
|
|
|
|
+
|
|
|
|
|
+ expectedIPNet := net.IPNet{IP: record.IP}
|
|
|
|
|
+ mockAlloc.On("Free", expectedIPNet).Return(nil)
|
|
|
|
|
+
|
|
|
|
|
+ // Call Handler4 with RELEASE message
|
|
|
|
|
+ result, stop := pl.Handler4(req, resp)
|
|
|
|
|
+
|
|
|
|
|
+ assert.Nil(t, result, "Should return nil response for RELEASE")
|
|
|
|
|
+ assert.True(t, stop, "Should return true to stop processing")
|
|
|
|
|
+
|
|
|
|
|
+ _, exists = pl.Recordsv4[hwaddr.String()]
|
|
|
|
|
+ assert.False(t, exists, "Record should be removed from memory after release")
|
|
|
|
|
+
|
|
|
|
|
+ parsedRecords, parseErr := loadRecords(pl.leasedb)
|
|
|
|
|
+ if parseErr != nil {
|
|
|
|
|
+ t.Fatalf("Failed to load records after release: %v", parseErr)
|
|
|
|
|
+ }
|
|
|
|
|
+ _, exists = parsedRecords[hwaddr.String()]
|
|
|
|
|
+ assert.False(t, exists, "Record should be removed from storage after release")
|
|
|
|
|
+
|
|
|
|
|
+ mockAlloc.AssertExpectations(t)
|
|
|
|
|
+ mockAlloc.AssertNotCalled(t, "Allocate")
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestHandler4ReleaseAllocatorError(t *testing.T) {
|
|
|
|
|
+ db, parseErr := testDBSetup()
|
|
|
|
|
+ if parseErr != nil {
|
|
|
|
|
+ t.Fatalf("Failed to set up test DB: %v", parseErr)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ mockAlloc := &mockFailingAllocator{}
|
|
|
|
|
+
|
|
|
|
|
+ pl := PluginState{
|
|
|
|
|
+ leasedb: db,
|
|
|
|
|
+ Recordsv4: make(map[string]*Record),
|
|
|
|
|
+ allocator: mockAlloc,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ loadedRecords, err := loadRecords(db)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatalf("Failed to load records: %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+ pl.Recordsv4 = loadedRecords
|
|
|
|
|
+
|
|
|
|
|
+ hwaddr, _ := net.ParseMAC(records[1].mac)
|
|
|
|
|
+ req := &dhcpv4.DHCPv4{
|
|
|
|
|
+ ClientHWAddr: hwaddr,
|
|
|
|
|
+ }
|
|
|
|
|
+ req.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeRelease))
|
|
|
|
|
+
|
|
|
|
|
+ resp := &dhcpv4.DHCPv4{}
|
|
|
|
|
+
|
|
|
|
|
+ record := pl.Recordsv4[hwaddr.String()]
|
|
|
|
|
+ expectedIPNet := net.IPNet{IP: record.IP}
|
|
|
|
|
+
|
|
|
|
|
+ expectedError := fmt.Errorf("mock allocator free failure")
|
|
|
|
|
+ mockAlloc.On("Free", expectedIPNet).Return(expectedError)
|
|
|
|
|
+
|
|
|
|
|
+ // Call Handler4 - this should fail on allocator.Free()
|
|
|
|
|
+ result, stop := pl.Handler4(req, resp)
|
|
|
|
|
+
|
|
|
|
|
+ assert.Nil(t, result, "Should return nil on allocator failure")
|
|
|
|
|
+ assert.True(t, stop, "Should stop processing on allocator failure")
|
|
|
|
|
+
|
|
|
|
|
+ _, exists := pl.Recordsv4[hwaddr.String()]
|
|
|
|
|
+ assert.False(t, exists, "Record should be removed from memory even on allocator failure")
|
|
|
|
|
+
|
|
|
|
|
+ parsedRecords, parseErr := loadRecords(pl.leasedb)
|
|
|
|
|
+ if parseErr != nil {
|
|
|
|
|
+ t.Fatalf("Failed to load records after release: %v", parseErr)
|
|
|
|
|
+ }
|
|
|
|
|
+ _, exists = parsedRecords[hwaddr.String()]
|
|
|
|
|
+ assert.False(t, exists, "Record should be removed from storage even on allocator failure")
|
|
|
|
|
+
|
|
|
|
|
+ mockAlloc.AssertExpectations(t)
|
|
|
|
|
+ mockAlloc.AssertNotCalled(t, "Allocate")
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestHandler4ReleaseStorageError(t *testing.T) {
|
|
|
|
|
+ db, parseErr := testDBSetup()
|
|
|
|
|
+ if parseErr != nil {
|
|
|
|
|
+ t.Fatalf("Failed to set up test DB: %v", parseErr)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ mockAlloc := &mockAllocator{}
|
|
|
|
|
+
|
|
|
|
|
+ pl := PluginState{
|
|
|
|
|
+ leasedb: db,
|
|
|
|
|
+ Recordsv4: make(map[string]*Record),
|
|
|
|
|
+ allocator: mockAlloc,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ loadedRecords, err := loadRecords(db)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatalf("Failed to load records: %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+ pl.Recordsv4 = loadedRecords
|
|
|
|
|
+
|
|
|
|
|
+ hwaddr, _ := net.ParseMAC(records[1].mac)
|
|
|
|
|
+ req := &dhcpv4.DHCPv4{
|
|
|
|
|
+ ClientHWAddr: hwaddr,
|
|
|
|
|
+ }
|
|
|
|
|
+ req.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeRelease))
|
|
|
|
|
+
|
|
|
|
|
+ resp := &dhcpv4.DHCPv4{}
|
|
|
|
|
+
|
|
|
|
|
+ // Close the database to simulate storage failure
|
|
|
|
|
+ db.Close()
|
|
|
|
|
+
|
|
|
|
|
+ result, stop := pl.Handler4(req, resp)
|
|
|
|
|
+
|
|
|
|
|
+ assert.Nil(t, result, "Should return nil on storage failure")
|
|
|
|
|
+ assert.True(t, stop, "Should stop processing on storage failure")
|
|
|
|
|
+
|
|
|
|
|
+ _, exists := pl.Recordsv4[hwaddr.String()]
|
|
|
|
|
+ assert.True(t, exists, "Record should still exist in memory after storage failure")
|
|
|
|
|
+
|
|
|
|
|
+ mockAlloc.AssertNotCalled(t, "Free")
|
|
|
|
|
+ mockAlloc.AssertNotCalled(t, "Allocate")
|
|
|
|
|
+}
|