Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions internal/database/metadata_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,3 +362,42 @@ func TestMetadataCacheRoundTripsContentEncoding(t *testing.T) {
t.Fatalf("content encoding round-trip failed: %#v", got)
}
}

// On Postgres, fetched_at is a TIMESTAMP column without a zone. East of UTC
// it must still read back as the moment it was written, or metadata_ttl
// expires hours late. last_modified is an HTTP date in UTC and must keep it.
func TestMetadataCacheTimesOffUTC(t *testing.T) {
tokyo, err := time.LoadLocation("Asia/Tokyo")
if err != nil {
t.Skipf("time zone data unavailable: %v", err)
}
prev := time.Local
time.Local = tokyo
t.Cleanup(func() { time.Local = prev })

runWithBothDatabases(t, func(t *testing.T, db *DB) {
fetched := time.Now().Add(-90 * time.Minute)
modified := time.Date(2026, 10, 1, 12, 0, 0, 0, time.UTC)
entry := &MetadataCacheEntry{
Ecosystem: testEcosystemNPM,
Name: "tz",
StoragePath: "_metadata/npm/tz/metadata",
LastModified: sql.NullTime{Time: modified, Valid: true},
FetchedAt: sql.NullTime{Time: fetched, Valid: true},
}
if err := db.UpsertMetadataCache(entry); err != nil {
t.Fatalf("UpsertMetadataCache() error = %v", err)
}

got, err := db.GetMetadataCache(testEcosystemNPM, "tz")
if err != nil || got == nil {
t.Fatalf("GetMetadataCache() = %v, %v", got, err)
}
if diff := got.FetchedAt.Time.Sub(fetched).Abs(); diff > time.Second {
t.Errorf("fetched_at read back as %v, want %v", got.FetchedAt.Time, fetched)
}
if !got.LastModified.Time.Equal(modified) {
t.Errorf("last_modified read back as %v, want %v", got.LastModified.Time, modified)
}
})
}
9 changes: 8 additions & 1 deletion internal/database/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,13 @@ func (db *DB) GetMetadataCache(ecosystem, name string) (*MetadataCacheEntry, err

func (db *DB) UpsertMetadataCache(entry *MetadataCacheEntry) error {
now := time.Now()
// fetched_at is a TIMESTAMP without zone on Postgres, and lib/pq reads it
// back labelled UTC, so it has to be stored in UTC. last_modified already
// is, since it comes from an HTTP date.
fetchedAt := entry.FetchedAt
if fetchedAt.Valid {
fetchedAt.Time = fetchedAt.Time.UTC()
}
var query string

if db.dialect == DialectPostgres {
Expand Down Expand Up @@ -1162,7 +1169,7 @@ func (db *DB) UpsertMetadataCache(entry *MetadataCacheEntry) error {

_, err := db.Exec(query,
entry.Ecosystem, entry.Name, entry.StoragePath, entry.ETag, entry.Link,
entry.ContentType, entry.ContentEncoding, entry.ContentDigest, entry.Size, entry.LastModified, entry.FetchedAt, now, now,
entry.ContentType, entry.ContentEncoding, entry.ContentDigest, entry.Size, entry.LastModified, fetchedAt, now, now,
)
if err != nil {
return fmt.Errorf("upserting metadata cache: %w", err)
Expand Down
Loading