- Priority 1: Test NewSendEmailTask + NewSendPushTask (5 tests) - Priority 2: Test customHTTPErrorHandler — all 15+ branches (21 tests) - Priority 3: Extract Enqueuer interface + payload builders in worker pkg (5 tests) - Priority 4: Extract ClassifyFile/ComputeRelPath in migrate-encrypt (6 tests) - Priority 5: Define Handler interfaces, refactor to accept them, mock-based tests (14 tests) - Fix .gitignore: /worker instead of worker to stop ignoring internal/worker/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
627 lines
16 KiB
Go
627 lines
16 KiB
Go
package models
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
)
|
|
|
|
// setupModelsTestDB creates a minimal in-memory SQLite for model-level tests
|
|
// that require database interaction (e.g., BeforeCreate hooks).
|
|
func setupModelsTestDB(t *testing.T) *gorm.DB {
|
|
t.Helper()
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
|
|
Logger: logger.Default.LogMode(logger.Silent),
|
|
})
|
|
require.NoError(t, err)
|
|
err = db.AutoMigrate(&User{}, &AuthToken{}, &UserProfile{})
|
|
require.NoError(t, err)
|
|
return db
|
|
}
|
|
|
|
// === Residence model tests ===
|
|
|
|
func TestResidence_GetAllUsers(t *testing.T) {
|
|
owner := User{Username: "owner"}
|
|
owner.ID = 1
|
|
member1 := User{Username: "member1"}
|
|
member1.ID = 2
|
|
member2 := User{Username: "member2"}
|
|
member2.ID = 3
|
|
|
|
residence := &Residence{
|
|
OwnerID: owner.ID,
|
|
Owner: owner,
|
|
Users: []User{member1, member2},
|
|
}
|
|
|
|
allUsers := residence.GetAllUsers()
|
|
assert.Len(t, allUsers, 3)
|
|
assert.Equal(t, "owner", allUsers[0].Username)
|
|
}
|
|
|
|
func TestResidence_HasAccess(t *testing.T) {
|
|
owner := User{Username: "owner"}
|
|
owner.ID = 1
|
|
member := User{Username: "member"}
|
|
member.ID = 2
|
|
|
|
residence := &Residence{
|
|
OwnerID: owner.ID,
|
|
Owner: owner,
|
|
Users: []User{member},
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
userID uint
|
|
expected bool
|
|
}{
|
|
{"owner has access", 1, true},
|
|
{"member has access", 2, true},
|
|
{"stranger no access", 99, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equal(t, tt.expected, residence.HasAccess(tt.userID))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResidence_IsPrimaryOwner(t *testing.T) {
|
|
residence := &Residence{OwnerID: 1}
|
|
|
|
assert.True(t, residence.IsPrimaryOwner(1))
|
|
assert.False(t, residence.IsPrimaryOwner(2))
|
|
}
|
|
|
|
// === Document model tests ===
|
|
|
|
func TestDocument_TableName_DocumentImage(t *testing.T) {
|
|
di := DocumentImage{}
|
|
assert.Equal(t, "task_documentimage", di.TableName())
|
|
}
|
|
|
|
func TestDocument_IsWarrantyExpiringSoon(t *testing.T) {
|
|
future30 := time.Now().UTC().AddDate(0, 0, 15)
|
|
future90 := time.Now().UTC().AddDate(0, 0, 60)
|
|
past := time.Now().UTC().AddDate(0, 0, -5)
|
|
|
|
tests := []struct {
|
|
name string
|
|
doc Document
|
|
days int
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "warranty expiring within threshold",
|
|
doc: Document{DocumentType: DocumentTypeWarranty, ExpiryDate: &future30},
|
|
days: 30,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "warranty not expiring within threshold",
|
|
doc: Document{DocumentType: DocumentTypeWarranty, ExpiryDate: &future90},
|
|
days: 30,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "warranty already expired",
|
|
doc: Document{DocumentType: DocumentTypeWarranty, ExpiryDate: &past},
|
|
days: 30,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "non-warranty document",
|
|
doc: Document{DocumentType: DocumentTypeGeneral, ExpiryDate: &future30},
|
|
days: 30,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "warranty with nil expiry",
|
|
doc: Document{DocumentType: DocumentTypeWarranty, ExpiryDate: nil},
|
|
days: 30,
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equal(t, tt.expected, tt.doc.IsWarrantyExpiringSoon(tt.days))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDocument_IsWarrantyExpired(t *testing.T) {
|
|
past := time.Now().UTC().AddDate(0, 0, -5)
|
|
future := time.Now().UTC().AddDate(0, 0, 30)
|
|
|
|
tests := []struct {
|
|
name string
|
|
doc Document
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "expired warranty",
|
|
doc: Document{DocumentType: DocumentTypeWarranty, ExpiryDate: &past},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "active warranty",
|
|
doc: Document{DocumentType: DocumentTypeWarranty, ExpiryDate: &future},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "non-warranty",
|
|
doc: Document{DocumentType: DocumentTypeGeneral, ExpiryDate: &past},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "warranty nil expiry",
|
|
doc: Document{DocumentType: DocumentTypeWarranty, ExpiryDate: nil},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equal(t, tt.expected, tt.doc.IsWarrantyExpired())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDocumentType_Constants(t *testing.T) {
|
|
// Verify document type constants have expected values
|
|
assert.Equal(t, DocumentType("general"), DocumentTypeGeneral)
|
|
assert.Equal(t, DocumentType("warranty"), DocumentTypeWarranty)
|
|
assert.Equal(t, DocumentType("receipt"), DocumentTypeReceipt)
|
|
assert.Equal(t, DocumentType("contract"), DocumentTypeContract)
|
|
assert.Equal(t, DocumentType("insurance"), DocumentTypeInsurance)
|
|
assert.Equal(t, DocumentType("manual"), DocumentTypeManual)
|
|
}
|
|
|
|
// === Notification model tests ===
|
|
|
|
func TestNotification_TableName(t *testing.T) {
|
|
n := Notification{}
|
|
assert.Equal(t, "notifications_notification", n.TableName())
|
|
}
|
|
|
|
func TestNotificationPreference_TableName(t *testing.T) {
|
|
np := NotificationPreference{}
|
|
assert.Equal(t, "notifications_notificationpreference", np.TableName())
|
|
}
|
|
|
|
func TestAPNSDevice_TableName(t *testing.T) {
|
|
d := APNSDevice{}
|
|
assert.Equal(t, "push_notifications_apnsdevice", d.TableName())
|
|
}
|
|
|
|
func TestGCMDevice_TableName(t *testing.T) {
|
|
d := GCMDevice{}
|
|
assert.Equal(t, "push_notifications_gcmdevice", d.TableName())
|
|
}
|
|
|
|
func TestNotification_MarkAsRead(t *testing.T) {
|
|
n := &Notification{Read: false}
|
|
|
|
n.MarkAsRead()
|
|
assert.True(t, n.Read)
|
|
assert.NotNil(t, n.ReadAt)
|
|
}
|
|
|
|
func TestNotification_MarkAsSent(t *testing.T) {
|
|
n := &Notification{Sent: false}
|
|
|
|
n.MarkAsSent()
|
|
assert.True(t, n.Sent)
|
|
assert.NotNil(t, n.SentAt)
|
|
}
|
|
|
|
func TestNotificationType_Constants(t *testing.T) {
|
|
assert.Equal(t, NotificationType("task_due_soon"), NotificationTaskDueSoon)
|
|
assert.Equal(t, NotificationType("task_overdue"), NotificationTaskOverdue)
|
|
assert.Equal(t, NotificationType("task_completed"), NotificationTaskCompleted)
|
|
assert.Equal(t, NotificationType("task_assigned"), NotificationTaskAssigned)
|
|
assert.Equal(t, NotificationType("residence_shared"), NotificationResidenceShared)
|
|
assert.Equal(t, NotificationType("warranty_expiring"), NotificationWarrantyExpiring)
|
|
}
|
|
|
|
// === AuthToken model tests ===
|
|
|
|
func TestAuthToken_BeforeCreate_GeneratesKey(t *testing.T) {
|
|
db := setupModelsTestDB(t)
|
|
|
|
user := &User{
|
|
Username: "tokenuser",
|
|
Email: "token@test.com",
|
|
Password: "dummy",
|
|
IsActive: true,
|
|
}
|
|
err := db.Create(user).Error
|
|
require.NoError(t, err)
|
|
|
|
token := &AuthToken{UserID: user.ID}
|
|
err = db.Create(token).Error
|
|
require.NoError(t, err)
|
|
|
|
assert.NotEmpty(t, token.Key)
|
|
assert.Len(t, token.Key, 40) // 20 bytes = 40 hex chars
|
|
assert.False(t, token.Created.IsZero())
|
|
}
|
|
|
|
func TestAuthToken_BeforeCreate_PreservesExistingKey(t *testing.T) {
|
|
db := setupModelsTestDB(t)
|
|
|
|
user := &User{
|
|
Username: "tokenuser",
|
|
Email: "token@test.com",
|
|
Password: "dummy",
|
|
IsActive: true,
|
|
}
|
|
err := db.Create(user).Error
|
|
require.NoError(t, err)
|
|
|
|
existingKey := "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
|
|
token := &AuthToken{
|
|
Key: existingKey,
|
|
UserID: user.ID,
|
|
}
|
|
err = db.Create(token).Error
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, existingKey, token.Key)
|
|
}
|
|
|
|
func TestGetOrCreateToken_CreatesNew(t *testing.T) {
|
|
db := setupModelsTestDB(t)
|
|
|
|
user := &User{
|
|
Username: "newtoken",
|
|
Email: "newtoken@test.com",
|
|
Password: "dummy",
|
|
IsActive: true,
|
|
}
|
|
err := db.Create(user).Error
|
|
require.NoError(t, err)
|
|
|
|
token, err := GetOrCreateToken(db, user.ID)
|
|
require.NoError(t, err)
|
|
assert.NotEmpty(t, token.Key)
|
|
assert.Equal(t, user.ID, token.UserID)
|
|
}
|
|
|
|
func TestGetOrCreateToken_ReturnsExisting(t *testing.T) {
|
|
db := setupModelsTestDB(t)
|
|
|
|
user := &User{
|
|
Username: "existingtoken",
|
|
Email: "existingtoken@test.com",
|
|
Password: "dummy",
|
|
IsActive: true,
|
|
}
|
|
err := db.Create(user).Error
|
|
require.NoError(t, err)
|
|
|
|
token1, err := GetOrCreateToken(db, user.ID)
|
|
require.NoError(t, err)
|
|
|
|
token2, err := GetOrCreateToken(db, user.ID)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, token1.Key, token2.Key)
|
|
}
|
|
|
|
// === User model additional tests ===
|
|
|
|
func TestUser_SetPassword_And_CheckPassword_Integration(t *testing.T) {
|
|
user := &User{}
|
|
err := user.SetPassword("Password123")
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, user.CheckPassword("Password123"))
|
|
assert.False(t, user.CheckPassword("WrongPassword"))
|
|
assert.False(t, user.CheckPassword(""))
|
|
assert.False(t, user.CheckPassword("password123")) // case sensitive
|
|
}
|
|
|
|
// === Task model additional tests ===
|
|
|
|
func TestTask_IsOverdue_CancelledNotOverdue(t *testing.T) {
|
|
yesterday := time.Now().UTC().AddDate(0, 0, -2)
|
|
task := &Task{
|
|
DueDate: &yesterday,
|
|
IsCancelled: true,
|
|
}
|
|
assert.False(t, task.IsOverdue())
|
|
}
|
|
|
|
func TestTask_IsOverdue_ArchivedNotOverdue(t *testing.T) {
|
|
yesterday := time.Now().UTC().AddDate(0, 0, -2)
|
|
task := &Task{
|
|
DueDate: &yesterday,
|
|
IsArchived: true,
|
|
}
|
|
assert.False(t, task.IsOverdue())
|
|
}
|
|
|
|
func TestTask_IsOverdue_NoDueDateNotOverdue(t *testing.T) {
|
|
task := &Task{
|
|
DueDate: nil,
|
|
}
|
|
assert.False(t, task.IsOverdue())
|
|
}
|
|
|
|
func TestTask_IsOverdue_CompletedNotOverdue(t *testing.T) {
|
|
yesterday := time.Now().UTC().AddDate(0, 0, -2)
|
|
task := &Task{
|
|
DueDate: &yesterday,
|
|
NextDueDate: nil,
|
|
Completions: []TaskCompletion{{CompletedAt: time.Now().UTC()}},
|
|
}
|
|
assert.False(t, task.IsOverdue())
|
|
}
|
|
|
|
func TestTask_IsOverdue_CompletionCountNotOverdue(t *testing.T) {
|
|
yesterday := time.Now().UTC().AddDate(0, 0, -2)
|
|
task := &Task{
|
|
DueDate: &yesterday,
|
|
NextDueDate: nil,
|
|
CompletionCount: 1,
|
|
}
|
|
assert.False(t, task.IsOverdue())
|
|
}
|
|
|
|
func TestTask_IsOverdue_UsesNextDueDate(t *testing.T) {
|
|
// DueDate is overdue, but NextDueDate is in the future
|
|
pastDue := time.Now().UTC().AddDate(0, 0, -10)
|
|
futureDue := time.Now().UTC().AddDate(0, 0, 10)
|
|
task := &Task{
|
|
DueDate: &pastDue,
|
|
NextDueDate: &futureDue,
|
|
}
|
|
assert.False(t, task.IsOverdue())
|
|
}
|
|
|
|
func TestTask_IsDueSoon_CancelledNotDueSoon(t *testing.T) {
|
|
futureDue := time.Now().UTC().AddDate(0, 0, 5)
|
|
task := &Task{
|
|
DueDate: &futureDue,
|
|
IsCancelled: true,
|
|
}
|
|
assert.False(t, task.IsDueSoon(30))
|
|
}
|
|
|
|
func TestTask_IsDueSoon_NoDueDateNotDueSoon(t *testing.T) {
|
|
task := &Task{
|
|
DueDate: nil,
|
|
}
|
|
assert.False(t, task.IsDueSoon(30))
|
|
}
|
|
|
|
func TestTask_IsDueSoon_WithinThreshold(t *testing.T) {
|
|
futureDue := time.Now().UTC().AddDate(0, 0, 5)
|
|
task := &Task{
|
|
DueDate: &futureDue,
|
|
}
|
|
assert.True(t, task.IsDueSoon(30))
|
|
assert.True(t, task.IsDueSoon(10))
|
|
assert.False(t, task.IsDueSoon(3))
|
|
}
|
|
|
|
func TestTask_IsDueSoon_CompletedNotDueSoon(t *testing.T) {
|
|
futureDue := time.Now().UTC().AddDate(0, 0, 5)
|
|
task := &Task{
|
|
DueDate: &futureDue,
|
|
NextDueDate: nil,
|
|
Completions: []TaskCompletion{{CompletedAt: time.Now().UTC()}},
|
|
}
|
|
assert.False(t, task.IsDueSoon(30))
|
|
}
|
|
|
|
func TestTaskCompletionImage_TableName(t *testing.T) {
|
|
tci := TaskCompletionImage{}
|
|
assert.Equal(t, "task_taskcompletionimage", tci.TableName())
|
|
}
|
|
|
|
// === Subscription model additional tests ===
|
|
|
|
func TestSubscription_TableNames(t *testing.T) {
|
|
assert.Equal(t, "subscription_subscriptionsettings", SubscriptionSettings{}.TableName())
|
|
assert.Equal(t, "subscription_usersubscription", UserSubscription{}.TableName())
|
|
assert.Equal(t, "subscription_upgradetrigger", UpgradeTrigger{}.TableName())
|
|
assert.Equal(t, "subscription_featurebenefit", FeatureBenefit{}.TableName())
|
|
assert.Equal(t, "subscription_promotion", Promotion{}.TableName())
|
|
assert.Equal(t, "subscription_tierlimits", TierLimits{}.TableName())
|
|
}
|
|
|
|
func TestSubscription_IsActive(t *testing.T) {
|
|
future := time.Now().UTC().Add(24 * time.Hour)
|
|
past := time.Now().UTC().Add(-24 * time.Hour)
|
|
|
|
tests := []struct {
|
|
name string
|
|
sub *UserSubscription
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "pro with future expiry is active",
|
|
sub: &UserSubscription{Tier: TierPro, ExpiresAt: &future},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "pro with nil expiry is active",
|
|
sub: &UserSubscription{Tier: TierPro, ExpiresAt: nil},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "pro with past expiry is not active",
|
|
sub: &UserSubscription{Tier: TierPro, ExpiresAt: &past},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "free with active trial is active",
|
|
sub: &UserSubscription{Tier: TierFree, TrialEnd: &future},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "free without trial is not active",
|
|
sub: &UserSubscription{Tier: TierFree},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equal(t, tt.expected, tt.sub.IsActive())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSubscription_SubscriptionSource(t *testing.T) {
|
|
sub := &UserSubscription{Platform: "ios"}
|
|
assert.Equal(t, "ios", sub.SubscriptionSource())
|
|
}
|
|
|
|
func TestPromotion_IsCurrentlyActive(t *testing.T) {
|
|
now := time.Now().UTC()
|
|
|
|
tests := []struct {
|
|
name string
|
|
promo Promotion
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "active promotion within dates",
|
|
promo: Promotion{
|
|
IsActive: true,
|
|
StartDate: now.Add(-1 * time.Hour),
|
|
EndDate: now.Add(1 * time.Hour),
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "inactive promotion",
|
|
promo: Promotion{
|
|
IsActive: false,
|
|
StartDate: now.Add(-1 * time.Hour),
|
|
EndDate: now.Add(1 * time.Hour),
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "promotion not yet started",
|
|
promo: Promotion{
|
|
IsActive: true,
|
|
StartDate: now.Add(1 * time.Hour),
|
|
EndDate: now.Add(2 * time.Hour),
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "promotion already ended",
|
|
promo: Promotion{
|
|
IsActive: true,
|
|
StartDate: now.Add(-2 * time.Hour),
|
|
EndDate: now.Add(-1 * time.Hour),
|
|
},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equal(t, tt.expected, tt.promo.IsCurrentlyActive())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetDefaultFreeLimits(t *testing.T) {
|
|
limits := GetDefaultFreeLimits()
|
|
assert.Equal(t, TierFree, limits.Tier)
|
|
require.NotNil(t, limits.PropertiesLimit)
|
|
require.NotNil(t, limits.TasksLimit)
|
|
require.NotNil(t, limits.ContractorsLimit)
|
|
require.NotNil(t, limits.DocumentsLimit)
|
|
assert.Equal(t, 1, *limits.PropertiesLimit)
|
|
assert.Equal(t, 10, *limits.TasksLimit)
|
|
assert.Equal(t, 0, *limits.ContractorsLimit)
|
|
assert.Equal(t, 0, *limits.DocumentsLimit)
|
|
}
|
|
|
|
func TestGetDefaultProLimits(t *testing.T) {
|
|
limits := GetDefaultProLimits()
|
|
assert.Equal(t, TierPro, limits.Tier)
|
|
assert.Nil(t, limits.PropertiesLimit)
|
|
assert.Nil(t, limits.TasksLimit)
|
|
assert.Nil(t, limits.ContractorsLimit)
|
|
assert.Nil(t, limits.DocumentsLimit)
|
|
}
|
|
|
|
// === ConfirmationCode additional tests ===
|
|
|
|
func TestConfirmationCode_TableName(t *testing.T) {
|
|
cc := ConfirmationCode{}
|
|
assert.Equal(t, "user_confirmationcode", cc.TableName())
|
|
}
|
|
|
|
// === PasswordResetCode additional tests ===
|
|
|
|
func TestPasswordResetCode_TableName(t *testing.T) {
|
|
prc := PasswordResetCode{}
|
|
assert.Equal(t, "user_passwordresetcode", prc.TableName())
|
|
}
|
|
|
|
// === Social Auth TableName tests ===
|
|
|
|
func TestAppleSocialAuth_TableName(t *testing.T) {
|
|
a := AppleSocialAuth{}
|
|
assert.Equal(t, "user_applesocialauth", a.TableName())
|
|
}
|
|
|
|
func TestGoogleSocialAuth_TableName(t *testing.T) {
|
|
g := GoogleSocialAuth{}
|
|
assert.Equal(t, "user_googlesocialauth", g.TableName())
|
|
}
|
|
|
|
// === BaseModel tests ===
|
|
|
|
func TestBaseModel_BeforeCreate(t *testing.T) {
|
|
b := &BaseModel{}
|
|
err := b.BeforeCreate(nil)
|
|
require.NoError(t, err)
|
|
|
|
assert.False(t, b.CreatedAt.IsZero())
|
|
assert.False(t, b.UpdatedAt.IsZero())
|
|
}
|
|
|
|
func TestBaseModel_BeforeCreate_PreservesExisting(t *testing.T) {
|
|
existingTime := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
b := &BaseModel{
|
|
CreatedAt: existingTime,
|
|
UpdatedAt: existingTime,
|
|
}
|
|
err := b.BeforeCreate(nil)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, existingTime, b.CreatedAt)
|
|
assert.Equal(t, existingTime, b.UpdatedAt)
|
|
}
|
|
|
|
func TestBaseModel_BeforeUpdate(t *testing.T) {
|
|
oldTime := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
b := &BaseModel{
|
|
UpdatedAt: oldTime,
|
|
}
|
|
err := b.BeforeUpdate(nil)
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, b.UpdatedAt.After(oldTime))
|
|
}
|