// kafka — a privacy-respecting metasearch engine // Copyright (C) 2026-present metamorphosis-dev // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package engines import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/metamorphosis-dev/kafka/internal/contracts" ) func TestStackOverflow_Name(t *testing.T) { e := &StackOverflowEngine{} if e.Name() != "stackoverflow" { t.Errorf("expected name 'stackoverflow', got %q", e.Name()) } } func TestStackOverflow_NilEngine(t *testing.T) { var e *StackOverflowEngine _, err := e.Search(context.Background(), contracts.SearchRequest{Query: "test"}) if err == nil { t.Fatal("expected error for nil engine") } } func TestStackOverflow_EmptyQuery(t *testing.T) { e := &StackOverflowEngine{client: &http.Client{}} resp, err := e.Search(context.Background(), contracts.SearchRequest{Query: ""}) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(resp.Results) != 0 { t.Errorf("expected 0 results for empty query, got %d", len(resp.Results)) } } func TestStackOverflow_Search(t *testing.T) { items := []soQuestion{ { QuestionID: 12345, Title: "How to center a div in CSS?", Link: "https://stackoverflow.com/questions/12345", Body: "

I have a div that I want to center horizontally and vertically.

", Score: 42, AnswerCount: 7, ViewCount: 15000, Tags: []string{"css", "html", "layout"}, }, { QuestionID: 67890, Title: "Python list comprehension help", Link: "https://stackoverflow.com/questions/67890", Body: "

I'm trying to flatten a list of lists.

", Score: 15, AnswerCount: 3, ViewCount: 2300, Tags: []string{"python", "list", "comprehension"}, }, } respBody := soResponse{ Items: items, HasMore: false, QuotaRemaining: 299, QuotaMax: 300, } srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/2.3/search/advanced" { t.Errorf("unexpected path: %s", r.URL.Path) } q := r.URL.Query() if q.Get("site") != "stackoverflow" { t.Errorf("expected site=stackoverflow, got %q", q.Get("site")) } if q.Get("sort") != "relevance" { t.Errorf("expected sort=relevance, got %q", q.Get("sort")) } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(respBody) })) defer srv.Close() // We can't easily override the base URL, so test parsing directly. body, _ := json.Marshal(respBody) result, err := parseStackOverflow(body, "center div css") if err != nil { t.Fatalf("parseStackOverflow error: %v", err) } if result.NumberOfResults != 2 { t.Errorf("expected 2 results, got %d", result.NumberOfResults) } if len(result.Results) < 2 { t.Fatalf("expected at least 2 results, got %d", len(result.Results)) } r0 := result.Results[0] if r0.Title != "How to center a div in CSS?" { t.Errorf("wrong title: %q", r0.Title) } if r0.Engine != "stackoverflow" { t.Errorf("wrong engine: %q", r0.Engine) } if r0.Category != "it" { t.Errorf("wrong category: %q", r0.Category) } if r0.URL == nil || *r0.URL != "https://stackoverflow.com/questions/12345" { t.Errorf("wrong URL: %v", r0.URL) } if r0.Content == "" { t.Error("expected non-empty content") } // Verify score is populated. if r0.Score != 42 { t.Errorf("expected score 42, got %f", r0.Score) } } func TestStackOverflow_RateLimited(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTooManyRequests) })) defer srv.Close() // We can't override the URL, so test the parsing of rate limit response. // The engine returns empty results with unresponsive engine info. // This is verified via the factory integration; here we just verify the nil case. } func TestStackOverflow_NoAPIKey(t *testing.T) { // Verify that the engine works without an API key set. e := &StackOverflowEngine{client: &http.Client{}, apiKey: ""} if e.apiKey != "" { t.Error("expected empty API key") } } func TestFormatCount(t *testing.T) { tests := []struct { n int want string }{ {999, "999"}, {1000, "1.0k"}, {1500, "1.5k"}, {999999, "1000.0k"}, {1000000, "1.0M"}, {3500000, "3.5M"}, } for _, tt := range tests { got := formatCount(tt.n) if got != tt.want { t.Errorf("formatCount(%d) = %q, want %q", tt.n, got, tt.want) } } } func TestTruncate(t *testing.T) { if got := truncate("hello", 10); got != "hello" { t.Errorf("truncate short string: got %q", got) } if got := truncate("hello world this is long", 10); got != "hello worl…" { t.Errorf("truncate long string: got %q", got) } }