H1's doctrine applied to the annotation service, which had been left on setters: NewMediaHandler/NewKoboHandler/NewKOReaderHandler/ NewSyncQueueProcessor(WithConfig) now take it as a parameter; the four SetAnnotationService setters are deleted; wiring updated (main.go, router/sync.go, test harness). With the service unskippable, the 18 nil-guards and their plain-query fallbacks die: - media.go: the create fallbacks for notes/highlights/bookmarks (the bookmark one was the H3 census's Bug #1 — it dropped percentage/chapter_reference the sync path carries), the highlight PUT fallback, and the three delete-path plain deletes (tombstone is now the only delete semantics). - kobo.go: both markup-ingest annotation loops (the bookmark fallback wrote the text into media_notes — a category error the fallback never should have had). - koreader.go: all three push-stack guards. - queue.go: the three "annotation service not available" errors in syncNote/syncHighlight/syncBookmark. Behavior note: the deleted highlight create/PUT fallbacks were the only paths passing note_id to the plain query — the sync service has no NoteID field, so the web's highlight→note link was already being dropped on the live path; this commit makes that pre-existing limitation explicit rather than papering it over (surfacing note_id on SaveHighlightRequest is left for a behavior commit that needs it). Pins (now 20 incl. the 3 annotation CRUD suites) + H1 tests + both unit suites green.
60 lines
1.8 KiB
Go
60 lines
1.8 KiB
Go
package sync
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestCalculateNextRetry(t *testing.T) {
|
|
processor := NewSyncQueueProcessor(nil, nil, nil)
|
|
|
|
tests := []struct {
|
|
name string
|
|
attempts int32
|
|
minDelay time.Duration
|
|
maxDelay time.Duration
|
|
}{
|
|
{"Attempt 0", 0, -100 * time.Millisecond, 1 * time.Second},
|
|
{"Attempt 1", 1, 59 * time.Second, 61 * time.Second},
|
|
{"Attempt 2", 2, 4*time.Minute + 59*time.Second, 5*time.Minute + 1*time.Second},
|
|
{"Attempt 3", 3, 14*time.Minute + 59*time.Second, 15*time.Minute + 1*time.Second},
|
|
{"Attempt 4", 4, 59*time.Minute + 59*time.Second, 60*time.Minute + 1*time.Second},
|
|
{"Attempt 5", 5, 23*time.Hour + 59*time.Minute, 24*time.Hour + 1*time.Minute},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
nextRetry := processor.calculateNextRetry(tt.attempts)
|
|
delay := nextRetry.Sub(time.Now())
|
|
|
|
assert.GreaterOrEqual(t, delay, tt.minDelay, "delay should be at least minDelay")
|
|
assert.LessOrEqual(t, delay, tt.maxDelay, "delay should be at most maxDelay")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSyncTypeConstants(t *testing.T) {
|
|
assert.Equal(t, "progress", SyncTypeProgress)
|
|
assert.Equal(t, "note", SyncTypeNote)
|
|
assert.Equal(t, "highlight", SyncTypeHighlight)
|
|
assert.Equal(t, "bookmark", SyncTypeBookmark)
|
|
}
|
|
|
|
func TestSyncStatusConstants(t *testing.T) {
|
|
assert.Equal(t, "pending", SyncStatusPending)
|
|
assert.Equal(t, "processing", SyncStatusProcessing)
|
|
assert.Equal(t, "completed", SyncStatusCompleted)
|
|
assert.Equal(t, "failed", SyncStatusFailed)
|
|
}
|
|
|
|
func TestPriorityConstants(t *testing.T) {
|
|
assert.Equal(t, 1, PriorityUserInitiated)
|
|
assert.Equal(t, 2, PriorityBookCompletion)
|
|
assert.Equal(t, 3, PriorityCriticalNote)
|
|
assert.Equal(t, 5, PriorityPageTurn)
|
|
assert.Equal(t, 7, PriorityCheckpoint)
|
|
assert.Equal(t, 10, PriorityBackgroundSync)
|
|
}
|