After transcript caching (ADR-021) and the Shorts filter (ADR-023), the remaining caption waste is the first fetch on every new video of a channel that never has English captions — each costs one rate-limited fetch to resolve to "none", and on a throttled IP churns the backoff machinery first. Remember, per (user, channel), a streak of consecutive no-caption outcomes (channel_caption_state, migration 016, RLS-scoped). Once it reaches TAPIR_CHANNEL_CAPTIONLESS_THRESHOLD (default 5) the channel is suppressed — videos discovered/listed but not caption-fetched — for TAPIR_CHANNEL_CAPTIONLESS_WINDOW (default 14d), then one is re-probed (auto-recovery). A successful fetch resets the streak; a 429 does not count; an explicit manual request bypasses suppression. threshold=0 disables. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
68 lines
2.3 KiB
Go
68 lines
2.3 KiB
Go
package store_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestChannelCaptionMemory_SuppressesAfterThreshold(t *testing.T) {
|
|
ctx := context.Background()
|
|
s := newStore(t)
|
|
super := rawPool(t)
|
|
resetDB(t, super)
|
|
seedUser(t, super, userA)
|
|
|
|
const threshold = 3
|
|
window := time.Hour
|
|
|
|
// Below threshold: not yet suppressed.
|
|
require.NoError(t, s.RecordChannelCaptionOutcome(ctx, userA, "chanX", false, threshold, window))
|
|
require.NoError(t, s.RecordChannelCaptionOutcome(ctx, userA, "chanX", false, threshold, window))
|
|
got, err := s.CaptionlessChannels(ctx, userA)
|
|
require.NoError(t, err)
|
|
require.NotContains(t, got, "chanX", "2 < threshold 3: not suppressed yet")
|
|
|
|
// Crossing the threshold suppresses the channel.
|
|
require.NoError(t, s.RecordChannelCaptionOutcome(ctx, userA, "chanX", false, threshold, window))
|
|
got, err = s.CaptionlessChannels(ctx, userA)
|
|
require.NoError(t, err)
|
|
require.Contains(t, got, "chanX", "3 consecutive no-caption results suppress the channel")
|
|
|
|
// A successful caption fetch resets it.
|
|
require.NoError(t, s.RecordChannelCaptionOutcome(ctx, userA, "chanX", true, threshold, window))
|
|
got, err = s.CaptionlessChannels(ctx, userA)
|
|
require.NoError(t, err)
|
|
require.NotContains(t, got, "chanX", "a captioned video clears suppression")
|
|
}
|
|
|
|
func TestChannelCaptionMemory_WindowExpiryReProbes(t *testing.T) {
|
|
ctx := context.Background()
|
|
s := newStore(t)
|
|
super := rawPool(t)
|
|
resetDB(t, super)
|
|
seedUser(t, super, userA)
|
|
|
|
// A negative window means captionless_until lands in the past — modelling an
|
|
// elapsed suppression window, which must make the channel eligible again.
|
|
require.NoError(t, s.RecordChannelCaptionOutcome(ctx, userA, "chanY", false, 1, -time.Hour))
|
|
got, err := s.CaptionlessChannels(ctx, userA)
|
|
require.NoError(t, err)
|
|
require.NotContains(t, got, "chanY", "an expired window re-enables the channel for a re-probe")
|
|
}
|
|
|
|
func TestChannelCaptionMemory_DisabledThresholdIsNoOp(t *testing.T) {
|
|
ctx := context.Background()
|
|
s := newStore(t)
|
|
super := rawPool(t)
|
|
resetDB(t, super)
|
|
seedUser(t, super, userA)
|
|
|
|
require.NoError(t, s.RecordChannelCaptionOutcome(ctx, userA, "chanZ", false, 0, time.Hour))
|
|
got, err := s.CaptionlessChannels(ctx, userA)
|
|
require.NoError(t, err)
|
|
require.Empty(t, got, "threshold 0 disables the memory — nothing recorded")
|
|
}
|