package services import ( "encoding/base64" "fmt" "net/http" "net/http/httptest" "net/url" "strings" "sync/atomic" "testing" "time" ) func TestExtractVideoPreviewURLFindsEnvatoPreview(t *testing.T) { html := `` got := firstNonEmpty(extractJSONLDValue(html, "contentUrl"), extractVideoPreviewURL(html)) want := "https://video-previews.elements.envatousercontent.com/ad0a3abc-7eb0-4075-8f68-8198f9a08777/watermarked_preview/watermarked_preview.mp4" if got != want { t.Fatalf("expected %q, got %q", want, got) } } func TestExtractVideoPreviewURLFindsGenericM3U8(t *testing.T) { html := `` got := extractVideoPreviewURL(html) want := "https://cdn.example.com/preview/master.m3u8?token=abc" if got != want { t.Fatalf("expected %q, got %q", want, got) } } func TestDeriveEnvatoPreviewFromThumbnail(t *testing.T) { thumb := "https://elements-resized.envatousercontent.com/elements-video-cover-images/ad0a3abc-7eb0-4075-8f68-8198f9a08777/video_preview/video_preview_0000.jpg?w=1200&h=630" got := deriveEnvatoPreviewFromThumbnail(thumb) want := "https://elements-resized.envatousercontent.com/elements-video-cover-images/ad0a3abc-7eb0-4075-8f68-8198f9a08777/watermarked_preview/watermarked_preview.mp4" if got != want { t.Fatalf("expected %q, got %q", want, got) } } func TestExtractEnvatoPreviewFromHydration(t *testing.T) { payload := `{"contentUrl":"https://video-previews.elements.envatousercontent.com/example/watermarked_preview/watermarked_preview.mp4"}` html := `` got := extractEnvatoPreviewFromHydration(html) want := "https://video-previews.elements.envatousercontent.com/example/watermarked_preview/watermarked_preview.mp4" if got != want { t.Fatalf("expected %q, got %q", want, got) } } func TestCollectEnvatoPreviewURLFindsOgVideo(t *testing.T) { html := `` got := collectEnvatoPreviewURL(html, "", "", "") want := "https://video-previews.elements.envatousercontent.com/example/watermarked_preview/watermarked_preview.mp4" if got != want { t.Fatalf("expected %q, got %q", want, got) } } func TestIsUsefulGoogleVideoResultRejectsMusicResults(t *testing.T) { result := SearchResult{ Title: "Couple Friendly Sad Bgm Movie Best Bgm", Link: "https://www.youtube.com/watch?v=LGP4wiXSw8c", Snippet: "romantic bgm soundtrack", } if isUsefulGoogleVideoResult(result) { t.Fatal("expected bgm/music result to be rejected") } } func TestExtractVideoObjectJSONLD(t *testing.T) { html := `` meta := extractVideoObjectJSONLD(html) if meta.Name != "Smiling Man and Woman Waving at Camera" { t.Fatalf("unexpected name: %#v", meta) } if meta.ContentURL == "" || meta.ThumbnailURL == "" || meta.Description == "" { t.Fatalf("expected full video object metadata, got %#v", meta) } } func TestCleanArtgridTitle(t *testing.T) { got := cleanArtgridTitle("movie film moving slowly from a reel by Arthur Cauty | Royalty Free Stock Footage – Artgrid.io") want := "movie film moving slowly from a reel" if got != want { t.Fatalf("expected %q, got %q", want, got) } } func TestCanonicalizeArtgridLinkFromArtlist(t *testing.T) { got := canonicalizeArtgridLink("https://artlist.io/stock-footage/clip/movie-film-moving-slowly-from-a-reel/114756") want := "https://artgrid.io/clip/114756/movie-film-moving-slowly-from-a-reel" if got != want { t.Fatalf("expected %q, got %q", want, got) } } func TestIsRenderableArtgridResultAcceptsArtlistCanonical(t *testing.T) { if !isRenderableArtgridResult(SearchResult{Link: "https://artlist.io/stock-footage/clip/movie-film-moving-slowly-from-a-reel/114756"}) { t.Fatal("expected artlist canonical clip URL to be accepted for Artgrid collector") } } func TestBuildArtgridQueriesIncludesArtlistCanonicalDomain(t *testing.T) { queries := buildArtgridQueries("friendly couple") found := false for _, query := range queries { if strings.Contains(query, "site:artlist.io/stock-footage/clip/") { found = true break } } if !found { t.Fatal("expected Artgrid queries to include artlist canonical domain") } } func TestIsMatchingArtgridClipPageRejectsHomepage(t *testing.T) { html := `` if isMatchingArtgridClipPage(html, "114756") { t.Fatal("expected generic Artgrid homepage HTML to be rejected as a clip page") } } func TestIsMatchingArtgridClipPageAcceptsBodySignals(t *testing.T) { html := `Night City | Stock Video Footage - Artgrid.io` if !isMatchingArtgridClipPage(html, "6600269") { t.Fatal("expected body/title signal Artgrid HTML to be accepted") } } func TestLowValueThumbnailDetection(t *testing.T) { if !IsLowValueThumbnail("https://example.com/favicon.ico") { t.Fatal("expected favicon to be low-value thumbnail") } if IsLowValueThumbnail("https://i.ytimg.com/vi/abcd1234xyz/hqdefault.jpg") { t.Fatal("expected youtube thumbnail to be usable") } } func TestGoogleVideoCollectorPrefersYouTubeDerivedThumbnail(t *testing.T) { result := googleVideoCollector{}.Enrich(nil, SearchResult{ Link: "https://www.youtube.com/watch?v=dQw4w9WgXcQ", ThumbnailURL: "https://example.com/some-search-thumb.jpg", }) if result.ThumbnailURL != "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg" { t.Fatalf("expected derived youtube thumbnail, got %q", result.ThumbnailURL) } } func TestGeminiCandidateLimitNeverExceedsCandidates(t *testing.T) { if got := GeminiCandidateLimit(9); got != 9 { t.Fatalf("expected Gemini limit to stay within candidate count, got %d", got) } } func TestLimitCollectorQueriesUsesSmallerBudgetForMissingPass(t *testing.T) { queries := []string{"a", "b", "c", "d"} got := limitCollectorQueries("Artgrid", queries, true) if len(got) != 4 { t.Fatalf("expected 4 queries for missing-pass Artgrid collector, got %d", len(got)) } got = limitCollectorQueries("Google Video", queries, false) if len(got) != 4 { t.Fatalf("expected 4 queries for Google Video collector, got %d", len(got)) } } func TestSearchServiceFetchCacheRoundTrip(t *testing.T) { service := NewSearchService("http://example.com", "", "") service.setCachedFetchResult("html\nhttps://example.com/item", "", time.Minute) body, ok := service.getCachedFetchResult("html\nhttps://example.com/item") if !ok { t.Fatal("expected cached fetch result") } if body != "" { t.Fatalf("unexpected cached body: %q", body) } } func TestSplitSearchDeadlinesReservesEnrichmentWindow(t *testing.T) { deadline := time.Now().Add(20 * time.Second) collectionDeadline, enrichmentDeadline := splitSearchDeadlines(deadline) if enrichmentDeadline.IsZero() { t.Fatal("expected enrichment deadline to be preserved") } if !collectionDeadline.Before(enrichmentDeadline) { t.Fatalf("expected collection deadline before enrichment deadline, got %v >= %v", collectionDeadline, enrichmentDeadline) } if gap := enrichmentDeadline.Sub(collectionDeadline); gap < searchEnrichmentReserve-500*time.Millisecond { t.Fatalf("expected reserve close to %v, got %v", searchEnrichmentReserve, gap) } } func TestSplitSearchDeadlinesDoesNotReserveWhenDeadlineIsTooClose(t *testing.T) { deadline := time.Now().Add(2 * time.Second) collectionDeadline, enrichmentDeadline := splitSearchDeadlines(deadline) if !collectionDeadline.Equal(enrichmentDeadline) { t.Fatalf("expected identical deadlines when budget is too tight, got %v and %v", collectionDeadline, enrichmentDeadline) } } func TestSearchServiceSkipsArtgridAPIAfter403(t *testing.T) { var apiRequests atomic.Int32 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch { case strings.HasPrefix(r.URL.Path, "/api/clip/details"): apiRequests.Add(1) http.Error(w, "forbidden", http.StatusForbidden) case strings.HasPrefix(r.URL.Path, "/clip/114756/"): w.Header().Set("Content-Type", "text/html; charset=utf-8") _, _ = fmt.Fprintf(w, `Friendly Couple | Stock Video Footage - Artgrid.io`, "114756") default: http.NotFound(w, r) } })) defer server.Close() service := NewSearchService(server.URL, "", "") serverURL, err := url.Parse(server.URL) if err != nil { t.Fatalf("failed to parse test server url: %v", err) } service.Client = &http.Client{ Transport: roundTripperFunc(func(req *http.Request) (*http.Response, error) { clone := req.Clone(req.Context()) if clone.URL.Host == "artgrid.io" { clone.URL.Scheme = serverURL.Scheme clone.URL.Host = serverURL.Host clone.Host = serverURL.Host } return http.DefaultTransport.RoundTrip(clone) }), } item := SearchResult{ Link: "https://artgrid.io/clip/114756/friendly-couple", Source: "Artgrid", } first := service.enrichArtgrid(item) second := service.enrichArtgrid(item) if apiRequests.Load() != 1 { t.Fatalf("expected artgrid API to be skipped after first 403, got %d requests", apiRequests.Load()) } if first.Title == "" || second.Title == "" { t.Fatalf("expected HTML fallback enrichment to preserve title, got %#v %#v", first, second) } } type roundTripperFunc func(*http.Request) (*http.Response, error) func (fn roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { return fn(req) }