package adapters import ( "context" "testing" "time" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/gorm" "github.com/h44z/wg-portal/internal/config" "github.com/h44z/wg-portal/internal/domain" ) func newTestDB(t *testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{}) require.NoError(t, err) return db } func TestUpsertUser_SetsCreatedAtWhenZero(t *testing.T) { db := newTestDB(t) require.NoError(t, db.AutoMigrate(&domain.User{}, &domain.UserAuthentication{}, &domain.UserWebauthnCredential{})) repo := &SqlRepo{db: db, cfg: &config.Config{}} ui := domain.SystemAdminContextUserInfo() user := &domain.User{ Identifier: "test-user", Email: "test@example.com", // CreatedAt is zero } err := repo.upsertUser(ui, db, user) require.NoError(t, err) assert.False(t, user.CreatedAt.IsZero(), "CreatedAt should be set when it was zero") assert.Equal(t, ui.UserId(), user.UpdatedBy, "UpdatedBy should be set when it was empty") assert.WithinDuration(t, user.UpdatedAt, user.CreatedAt, time.Second, "CreatedAt should be close to UpdatedAt for new user") } func TestUpsertUser_PreservesExistingCreatedAt(t *testing.T) { db := newTestDB(t) require.NoError(t, db.AutoMigrate(&domain.User{}, &domain.UserAuthentication{}, &domain.UserWebauthnCredential{})) repo := &SqlRepo{db: db, cfg: &config.Config{}} ui := domain.SystemAdminContextUserInfo() originalTime := time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC) user := &domain.User{ Identifier: "test-user", Email: "test@example.com", BaseModel: domain.BaseModel{ CreatedAt: originalTime, CreatedBy: "original-creator", }, } err := repo.upsertUser(ui, db, user) require.NoError(t, err) assert.Equal(t, originalTime, user.CreatedAt, "CreatedAt should not be overwritten") assert.Equal(t, "original-creator", user.CreatedBy, "CreatedBy should not be overwritten") } func TestSaveUser_NewUserGetsCreatedAt(t *testing.T) { db := newTestDB(t) require.NoError(t, db.AutoMigrate(&domain.User{}, &domain.UserAuthentication{}, &domain.UserWebauthnCredential{})) repo := &SqlRepo{db: db, cfg: &config.Config{}} ctx := domain.SetUserInfo(context.Background(), domain.SystemAdminContextUserInfo()) before := time.Now().Add(-time.Second) err := repo.SaveUser(ctx, "new-user", func(u *domain.User) (*domain.User, error) { u.Email = "new@example.com" return u, nil }) require.NoError(t, err) var saved domain.User require.NoError(t, db.First(&saved, "identifier = ?", "new-user").Error) assert.False(t, saved.CreatedAt.IsZero(), "CreatedAt should not be zero") assert.True(t, saved.CreatedAt.After(before), "CreatedAt should be recent") assert.NotEmpty(t, saved.CreatedBy, "CreatedBy should be set") } func TestMigration_FixesZeroCreatedAt(t *testing.T) { db := newTestDB(t) // Manually create tables and seed schema version 3 require.NoError(t, db.AutoMigrate( &SysStat{}, &domain.User{}, &domain.UserAuthentication{}, &domain.Interface{}, &domain.Cidr{}, &domain.Peer{}, &domain.AuditEntry{}, &domain.UserWebauthnCredential{}, )) // Insert schema versions 1, 2, 3 so migration starts at 3 for v := uint64(1); v <= 3; v++ { require.NoError(t, db.Create(&SysStat{SchemaVersion: v, MigratedAt: time.Now()}).Error) } updatedAt := time.Date(2025, 6, 15, 10, 0, 0, 0, time.UTC) // Insert a user with zero created_at but valid updated_at require.NoError(t, db.Exec( "INSERT INTO users (identifier, email, created_at, updated_at) VALUES (?, ?, ?, ?)", "zero-user", "zero@example.com", time.Time{}, updatedAt, ).Error) // Run migration repo := &SqlRepo{db: db, cfg: &config.Config{}} require.NoError(t, repo.migrate()) // Verify created_at was backfilled from updated_at var user domain.User require.NoError(t, db.First(&user, "identifier = ?", "zero-user").Error) assert.Equal(t, updatedAt, user.CreatedAt, "created_at should be backfilled from updated_at") // Verify schema version advanced to 4 var latest SysStat require.NoError(t, db.Order("schema_version DESC").First(&latest).Error) assert.Equal(t, uint64(4), latest.SchemaVersion) } func TestMigration_DoesNotTouchValidCreatedAt(t *testing.T) { db := newTestDB(t) require.NoError(t, db.AutoMigrate( &SysStat{}, &domain.User{}, &domain.UserAuthentication{}, &domain.Interface{}, &domain.Cidr{}, &domain.Peer{}, &domain.AuditEntry{}, &domain.UserWebauthnCredential{}, )) for v := uint64(1); v <= 3; v++ { require.NoError(t, db.Create(&SysStat{SchemaVersion: v, MigratedAt: time.Now()}).Error) } createdAt := time.Date(2024, 3, 1, 8, 0, 0, 0, time.UTC) updatedAt := time.Date(2025, 6, 15, 10, 0, 0, 0, time.UTC) require.NoError(t, db.Exec( "INSERT INTO users (identifier, email, created_at, updated_at) VALUES (?, ?, ?, ?)", "valid-user", "valid@example.com", createdAt, updatedAt, ).Error) repo := &SqlRepo{db: db, cfg: &config.Config{}} require.NoError(t, repo.migrate()) var user domain.User require.NoError(t, db.First(&user, "identifier = ?", "valid-user").Error) assert.Equal(t, createdAt, user.CreatedAt, "valid created_at should not be modified") }