The 2026-10-21 wipe: a re-extraction pass NULLed audio_metadata on the m4b audiobooks — every affected book dropped its chapters, track manifest and duration and displayed as 1 second long (the reader also lost its manifest, since single-file books synthesize it from the payload). Guards on both writers of that column: - updateMediaItemWithMetadata (generic full-row pass): when the new extraction carries no audio payload, keep existing.AudioMetadata. Single-file m4b books route here, and a failed/absent extraction leaves AudioMeta nil. - updateAudioBookItem (focused payload write): an empty payload (no tracks, no chapters, no duration) over a populated row is a failed extraction — refuse the write with a loud warning and let the next scan retry. audioPayloadIsEmpty() pins the empty-payload predicate (unit-tested): the column is only ever replaced by fresh data from a real audio extraction, never by absence of one.
34 lines
1.3 KiB
Go
34 lines
1.3 KiB
Go
package services
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
// TestAudioPayloadIsEmpty pins the write-after-success guard on
|
|
// updateAudioBookItem (2026-10-21 incident: multi-file audiobooks
|
|
// surfaced as 1 second long because a failed re-extraction overwrote a
|
|
// populated audio_metadata). An empty payload — nil, or no tracks, no
|
|
// chapters, no duration — is the signature of a failed extraction and
|
|
// must never replace existing audio data.
|
|
func TestAudioPayloadIsEmpty(t *testing.T) {
|
|
require := func(cond bool, msg string) {
|
|
if !cond {
|
|
t.Fatal(msg)
|
|
}
|
|
}
|
|
|
|
require(audioPayloadIsEmpty(nil), "nil payload must read as empty")
|
|
require(audioPayloadIsEmpty(&AudioMetadataPayload{}), "zero-value payload must read as empty")
|
|
require(audioPayloadIsEmpty(&AudioMetadataPayload{DurationSeconds: 0}), "zero-duration payload must read as empty")
|
|
|
|
// A real book always carries at least one of the audio facts.
|
|
require(!audioPayloadIsEmpty(&AudioMetadataPayload{DurationSeconds: 302.6}),
|
|
"duration-only payload (single-track book) is not empty")
|
|
require(!audioPayloadIsEmpty(&AudioMetadataPayload{
|
|
Tracks: []AudioTrack{{Index: 0, Path: "01.mp3"}},
|
|
}), "track manifest is not empty")
|
|
require(!audioPayloadIsEmpty(&AudioMetadataPayload{
|
|
Chapters: []AudioChapter{{Index: 0, Title: "Chapter 1"}},
|
|
}), "chapters are not empty")
|
|
}
|