mirror of
https://github.com/photoprism/photoprism.git
synced 2026-07-17 16:49:04 +00:00
Entity: Store created and updated timestamps with second precision
The schema stores created_at/updated_at as DATETIME without fractional seconds, so set gorm.NowFunc to entity.Now() (UTC truncated to the second). This keeps in-memory and persisted times in sync and makes timestamp comparisons behave the same on SQLite and MariaDB. Adapt the affected tests to compare with Sub() in a sane range or a clearly past base instead of strict sub-second ordering, and give the duplicate-client registry test distinct update times.
This commit is contained in:
parent
3c00d6fea9
commit
41ecbf9095
6 changed files with 28 additions and 12 deletions
|
|
@ -1,16 +1,15 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
)
|
||||
|
||||
// Set UTC as the default for created and updated timestamps.
|
||||
// Set UTC, truncated to whole seconds, as the default for created and updated
|
||||
// timestamps. The schema stores these as DATETIME without fractional seconds, so
|
||||
// generating second-precision values keeps in-memory and persisted times in sync
|
||||
// and makes timestamp comparisons behave the same on SQLite and MariaDB.
|
||||
func init() {
|
||||
gorm.NowFunc = func() time.Time {
|
||||
return UTC()
|
||||
}
|
||||
gorm.NowFunc = Now
|
||||
}
|
||||
|
||||
// Db returns the default *gorm.DB connection.
|
||||
|
|
|
|||
|
|
@ -26,7 +26,11 @@ func TestUTC(t *testing.T) {
|
|||
|
||||
t.Logf("NOW: %s, %s", utc.String(), utcGorm.String())
|
||||
|
||||
assert.True(t, utcGorm.After(utc))
|
||||
// gorm.NowFunc truncates to whole seconds, so it can trail the sub-second
|
||||
// UTC() reading by up to a second; assert they stay within a sane window.
|
||||
delta := utcGorm.Sub(utc)
|
||||
assert.Greater(t, delta, -time.Second)
|
||||
assert.Less(t, delta, time.Second)
|
||||
|
||||
if zone, offset := utcGorm.Zone(); zone != tz.UTC {
|
||||
t.Error("gorm time should be UTC")
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ func TestUpdate(t *testing.T) {
|
|||
})
|
||||
t.Run("NotUpdated", func(t *testing.T) {
|
||||
uid := rnd.GenerateUID(PhotoUID)
|
||||
m := &Photo{ID: missingPhotoID(), PhotoUID: uid, UpdatedAt: time.Now(), CreatedAt: Now(), PhotoTitle: "Foo"}
|
||||
m := &Photo{ID: missingPhotoID(), PhotoUID: uid, UpdatedAt: Now().Add(-time.Hour), CreatedAt: Now(), PhotoTitle: "Foo"}
|
||||
updatedAt := m.UpdatedAt
|
||||
|
||||
err := Update(m, "ID", "PhotoUID")
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package entity
|
|||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
|
|
@ -275,7 +276,11 @@ func TestMarker_Save(t *testing.T) {
|
|||
afterDate := m.UpdatedAt
|
||||
|
||||
assert.Equal(t, SrcMeta, m.MarkerSrc)
|
||||
assert.True(t, afterDate.After(initialDate))
|
||||
// Timestamps are stored with second precision, so a save within the same
|
||||
// second leaves UpdatedAt unchanged; assert it stays within a sane window.
|
||||
elapsed := afterDate.Sub(initialDate)
|
||||
assert.GreaterOrEqual(t, elapsed, time.Duration(0))
|
||||
assert.Less(t, elapsed, time.Minute)
|
||||
|
||||
if m.MarkerUID == "" || m.FileUID == "" {
|
||||
t.Errorf("UIDs should not be empty")
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package entity
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
|
|
@ -258,7 +259,11 @@ func TestService_Save(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
afterDate := model.UpdatedAt
|
||||
assert.True(t, afterDate.After(initialDate))
|
||||
// Timestamps are stored with second precision, so a save within the same
|
||||
// second leaves UpdatedAt unchanged; assert it stays within a sane window.
|
||||
elapsed := afterDate.Sub(initialDate)
|
||||
assert.GreaterOrEqual(t, elapsed, time.Duration(0))
|
||||
assert.Less(t, elapsed, time.Minute)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,8 +18,11 @@ func TestClientRegistry_DuplicateNamePrefersLatest(t *testing.T) {
|
|||
// Create two clients directly to simulate duplicates with same name.
|
||||
c1 := entity.NewClient().SetName("pp-dupe").SetRole(cluster.RoleInstance)
|
||||
assert.NoError(t, c1.Create())
|
||||
// Stagger times
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
// Backdate c1 so c2 is unambiguously the most recently updated. Timestamps are
|
||||
// stored with second precision, so a sub-second stagger would not separate them.
|
||||
assert.NoError(t, entity.UnscopedDb().Model(&entity.Client{}).
|
||||
Where("client_uid = ?", c1.ClientUID).
|
||||
UpdateColumn("updated_at", entity.Now().Add(-time.Hour)).Error)
|
||||
c2 := entity.NewClient().SetName("pp-dupe").SetRole(cluster.RoleService)
|
||||
assert.NoError(t, c2.Create())
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue