feat(youtube): map caption 429 to SourceRateLimited
The baseUrl fetch mapped every non-200 to SourceNone, recording a 429 as a permanent "no captions". 429 is the IP being rate-limited, not an absent transcript. Return SourceRateLimited (still a graceful degrade, no error) so the runner can retry after a backoff window. Other non-200s (403/404/5xx) stay SourceNone. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -388,6 +388,37 @@ func TestFetchTranscriptBaseURLForbiddenDegrades(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A 429 on the baseUrl fetch is the IP being rate-limited, NOT a permanent
|
||||
// absence of captions: it returns SourceRateLimited (no error, no text) so the
|
||||
// runner can record it and retry after a backoff window rather than recording a
|
||||
// false "no transcript".
|
||||
func TestFetchTranscriptRateLimitedReturnsSourceRateLimited(t *testing.T) {
|
||||
a, _ := newTestAdapter(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/youtubei/v1/player":
|
||||
base := "http://" + r.Host
|
||||
_, _ = w.Write([]byte(`{"captions":{"playerCaptionsTracklistRenderer":{"captionTracks":[` +
|
||||
`{"baseUrl":"` + base + `/api/timedtext?lang=en","languageCode":"en"}]}}}`))
|
||||
case "/api/timedtext":
|
||||
w.WriteHeader(http.StatusTooManyRequests)
|
||||
}
|
||||
})
|
||||
|
||||
tr, err := a.FetchTranscript(context.Background(), domain.Video{ID: "v1", UserID: "u1", ProviderVideoID: "vid1"})
|
||||
if err != nil {
|
||||
t.Fatalf("429 on baseUrl must degrade, not error: %v", err)
|
||||
}
|
||||
if tr.Source != domain.SourceRateLimited {
|
||||
t.Fatalf("expected SourceRateLimited on 429, got %q", tr.Source)
|
||||
}
|
||||
if tr.HasText() {
|
||||
t.Error("expected HasText() false for SourceRateLimited")
|
||||
}
|
||||
if tr.Content != "" {
|
||||
t.Errorf("expected empty content on 429, got %q", tr.Content)
|
||||
}
|
||||
}
|
||||
|
||||
// An empty baseUrl on the selected track degrades to SourceNone, never an error.
|
||||
func TestFetchTranscriptEmptyBaseURLDegrades(t *testing.T) {
|
||||
a, _ := newTestAdapter(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user