Batch H1 step 3a. The ceiling census (exhaustive UpdateUniversalProgress
grep) found 9 nil-guarded `if progressSvc != nil { funnel } else { raw
erase-write }` fallbacks — the survey's "four" under-counted kobo.go,
which has FOUR sites (Markup ReadingSync, Markup last-read-place,
AnalyticsGettests, SyncFromServer), plus one each in media.go, queue.go
and koreader.go. All unreachable: the service is created unconditionally
at main.go:127 and wired into every handler and the queue processor; the
test harness wires them too.
The erasure pattern is now unrepresentable:
- NewMediaHandler / NewKOReaderHandler / NewKoboHandler / both
NewSyncQueueProcessor flavors take the ProgressService as a REQUIRED
constructor arg; the SetProgressService setters are gone.
- Every fallback body (raw UpdateUniversalProgress with explicit-NULL
pgtypes for viewport/scroll/panel/reading_mode/zoom and no
audio_offset_seconds/context_text) is deleted; each write funnels
through SaveProgress with the LWW seed, so fields the writer doesn't
know survive.
- Removed the now-dead koreader pg-type helpers (textPtrToPgText,
intPtrToPgInt4, int64PtrToPgInt8) and their self-tests.
Behavior deltas (stored-row content, no wire shape changes —
KOReader/Kobo response pins stayed green):
- KOReader fallback used to write explicit NULL viewports and drop the
context_text the koplugin sends; the funneled mainline already maps
book.ContextText.
- Queue flushes no longer overwrite chapter_progress with the book
percentage (bogus data; preserved via the seed).
- A web PUT without percentage no longer writes 0% via the deleted
fallback; percentage seeds from the stored row.
Contract suite: KOReader×4 + Kobo×3 pins green.
108 lines
2.4 KiB
Go
108 lines
2.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestGetDeviceIcon_Kobo(t *testing.T) {
|
|
result := getDeviceIcon("kobo")
|
|
assert.Equal(t, "📚", result)
|
|
}
|
|
|
|
func TestGetDeviceIcon_KOReader(t *testing.T) {
|
|
result := getDeviceIcon("koreader")
|
|
assert.Equal(t, "📖", result)
|
|
}
|
|
|
|
func TestGetDeviceIcon_Kindle(t *testing.T) {
|
|
result := getDeviceIcon("kindle")
|
|
assert.Equal(t, "📱", result)
|
|
}
|
|
|
|
func TestGetDeviceIcon_Unknown(t *testing.T) {
|
|
result := getDeviceIcon("unknown")
|
|
assert.Equal(t, "📚", result)
|
|
|
|
result = getDeviceIcon("")
|
|
assert.Equal(t, "📚", result)
|
|
}
|
|
|
|
func TestFloat64PtrHelpers(t *testing.T) {
|
|
t.Run("pgtype float64 valid", func(t *testing.T) {
|
|
v := pgtype.Float8{Float64: 0.5, Valid: true}
|
|
result := float64PtrVal(v)
|
|
assert.NotNil(t, result)
|
|
assert.InDelta(t, 0.5, *result, 0.001)
|
|
})
|
|
t.Run("pgtype float64 invalid", func(t *testing.T) {
|
|
v := pgtype.Float8{Valid: false}
|
|
result := float64PtrVal(v)
|
|
assert.Nil(t, result)
|
|
})
|
|
t.Run("pgtype text valid", func(t *testing.T) {
|
|
v := pgtype.Text{String: "hello", Valid: true}
|
|
result := textPtrVal(v)
|
|
assert.NotNil(t, result)
|
|
assert.Equal(t, "hello", *result)
|
|
})
|
|
t.Run("pgtype text invalid", func(t *testing.T) {
|
|
v := pgtype.Text{Valid: false}
|
|
result := textPtrVal(v)
|
|
assert.Nil(t, result)
|
|
})
|
|
t.Run("pgtype int4 valid", func(t *testing.T) {
|
|
v := pgtype.Int4{Int32: 42, Valid: true}
|
|
result := int32PtrVal(v)
|
|
assert.NotNil(t, result)
|
|
assert.Equal(t, 42, *result)
|
|
})
|
|
t.Run("pgtype int4 invalid", func(t *testing.T) {
|
|
v := pgtype.Int4{Valid: false}
|
|
result := int32PtrVal(v)
|
|
assert.Nil(t, result)
|
|
})
|
|
t.Run("pgtype int8 valid", func(t *testing.T) {
|
|
v := pgtype.Int8{Int64: 10000, Valid: true}
|
|
result := int64PtrVal(v)
|
|
assert.NotNil(t, result)
|
|
assert.Equal(t, int64(10000), *result)
|
|
})
|
|
t.Run("pgtype int8 invalid", func(t *testing.T) {
|
|
v := pgtype.Int8{Valid: false}
|
|
result := int64PtrVal(v)
|
|
assert.Nil(t, result)
|
|
})
|
|
}
|
|
|
|
func float64PtrVal(v pgtype.Float8) *float64 {
|
|
if v.Valid {
|
|
return &v.Float64
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func textPtrVal(v pgtype.Text) *string {
|
|
if v.Valid {
|
|
return &v.String
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func int32PtrVal(v pgtype.Int4) *int {
|
|
if v.Valid {
|
|
val := int(v.Int32)
|
|
return &val
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func int64PtrVal(v pgtype.Int8) *int64 {
|
|
if v.Valid {
|
|
return &v.Int64
|
|
}
|
|
return nil
|
|
}
|