package services // Unit tests for the extractor-version (ev) mechanism (faceted plan // Phase 10): the wire normalization (absent = v1-or-unknown = 1) and the // per-scan catch-up budget. The full scan-state machine (ev mismatch → // re-extract once → stable) lives in cmd/server/tests // (TestExtractorVersionReextract) because the store paths need a real DB. import ( "testing" ) func TestManifestEvNormalization(t *testing.T) { if got := manifestEv(nil); got != 1 { t.Errorf("nil manifest rides v1: got %d, want 1", got) } cases := []struct { raw string want int }{ {`{"v":1}`, 1}, // old shape: ev absent → v1-or-unknown {`{"v":1,"ev":0}`, 1}, // explicitly unset → same {`{"v":1,"ev":1}`, 1}, // baseline parser stamp {`{"v":1,"ev":2}`, 2}, // post-bump stamp {`{"v":1,"entries":[],"ev":3}`, 3}, // sidecar-less ev home } for _, tc := range cases { m := decodeSidecarManifest([]byte(tc.raw)) if m == nil { t.Fatalf("%s must decode", tc.raw) } if got := manifestEv(m); got != tc.want { t.Errorf("manifestEv(%s) = %d, want %d", tc.raw, got, tc.want) } } } func TestEvBudgetTake(t *testing.T) { scanner := &MediaScanner{} scanner.evBudgetRemaining.Store(2) if !scanner.evBudgetTake() { t.Error("budget 2: first take must pass") } if !scanner.evBudgetTake() { t.Error("budget 1: second take must pass") } if scanner.evBudgetTake() { t.Error("budget 0: third take must fail") } if got := scanner.evBudgetRemaining.Load(); got != 0 { t.Errorf("budget must land at exactly 0, got %d", got) } } func TestEvBudgetTake_FailsFastAtZero(t *testing.T) { scanner := &MediaScanner{} scanner.evBudgetRemaining.Store(0) // Repeated takes at 0 must fail WITHOUT driving the counter negative // (the CAS loop only fires when cur > 0). for i := 0; i < 5; i++ { if scanner.evBudgetTake() { t.Fatalf("take %d must fail at budget 0", i+1) } } if got := scanner.evBudgetRemaining.Load(); got != 0 { t.Errorf("budget must stay 0, got %d", got) } }