From c1a7edcc9a1c4f73690e9653b5101adb85c91cc4 Mon Sep 17 00:00:00 2001 From: Jacopo Clark <37738506+clark-ja@users.noreply.github.com> Date: Wed, 25 Mar 2026 22:08:06 +0100 Subject: [PATCH] fix: prevent interface address clearing during startup (#651) Signed-off-by: jc <37738506+theguy147@users.noreply.github.com> Co-authored-by: jc <37738506+theguy147@users.noreply.github.com> --- internal/adapters/database.go | 4 +- internal/adapters/database_simple_test.go | 73 +++++++++++++++++++++++ 2 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 internal/adapters/database_simple_test.go diff --git a/internal/adapters/database.go b/internal/adapters/database.go index a668f7c..5fe5e23 100644 --- a/internal/adapters/database.go +++ b/internal/adapters/database.go @@ -482,7 +482,7 @@ func (r *SqlRepo) getOrCreateInterface( Identifier: id, } - err := tx.Attrs(interfaceDefaults).FirstOrCreate(&in, id).Error + err := tx.Preload("Addresses").Attrs(interfaceDefaults).FirstOrCreate(&in, id).Error if err != nil { return nil, err } @@ -691,7 +691,7 @@ func (r *SqlRepo) getOrCreatePeer(ui *domain.ContextUserInfo, tx *gorm.DB, id do Identifier: id, } - err := tx.Attrs(interfaceDefaults).FirstOrCreate(&peer, id).Error + err := tx.Preload("Addresses").Attrs(interfaceDefaults).FirstOrCreate(&peer, id).Error if err != nil { return nil, err } diff --git a/internal/adapters/database_simple_test.go b/internal/adapters/database_simple_test.go new file mode 100644 index 0000000..7aef163 --- /dev/null +++ b/internal/adapters/database_simple_test.go @@ -0,0 +1,73 @@ +package adapters + +import ( + "context" + "reflect" + "testing" + + "github.com/glebarez/sqlite" + "github.com/h44z/wg-portal/internal/config" + "github.com/h44z/wg-portal/internal/domain" + "github.com/stretchr/testify/require" + "gorm.io/gorm" + "gorm.io/gorm/schema" +) + +func init() { + schema.RegisterSerializer("encstr", dummySerializer{}) +} + +type dummySerializer struct{} + +func (dummySerializer) Scan(ctx context.Context, field *schema.Field, dst reflect.Value, dbValue any) error { + return nil +} + +func (dummySerializer) Value(ctx context.Context, field *schema.Field, dst reflect.Value, fieldValue any) (any, error) { + if fieldValue == nil { + return nil, nil + } + if v, ok := fieldValue.(string); ok { + return v, nil + } + if v, ok := fieldValue.(domain.PreSharedKey); ok { + return string(v), nil + } + return fieldValue, nil +} + +func TestSqlRepo_SaveInterface_Simple(t *testing.T) { + // Initialize in-memory database + db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{}) + require.NoError(t, err) + + // Migrate only what's needed for this test (avoids Peer and its encstr serializer) + require.NoError(t, db.AutoMigrate(&domain.Interface{}, &domain.Cidr{})) + + repo := &SqlRepo{db: db, cfg: &config.Config{}} + ctx := domain.SetUserInfo(context.Background(), domain.SystemAdminContextUserInfo()) + ifaceId := domain.InterfaceIdentifier("wg0") + + // 1. Create an interface with one address + addr, _ := domain.CidrFromString("10.0.0.1/24") + initialIface := &domain.Interface{ + Identifier: ifaceId, + Addresses: []domain.Cidr{addr}, + } + require.NoError(t, db.Create(initialIface).Error) + + // 2. Perform a "partial" update using SaveInterface (this is the buggy path) + err = repo.SaveInterface(ctx, ifaceId, func(in *domain.Interface) (*domain.Interface, error) { + in.DisplayName = "New Name" + return in, nil + }) + require.NoError(t, err) + + // 3. Verify that the address was NOT deleted + var finalIface domain.Interface + require.NoError(t, db.Preload("Addresses").First(&finalIface, "identifier = ?", ifaceId).Error) + + require.Equal(t, "New Name", finalIface.DisplayName) + require.Len(t, finalIface.Addresses, 1, "Address list should still have 1 entry!") + require.Equal(t, "10.0.0.1/24", finalIface.Addresses[0].Cidr) +}