feat: build Go-based SearXNG-compatible search service

Implement an API-first Go rewrite with local engine adapters, upstream fallback, and Nix-based tooling so searches can run without matching the original UI while preserving response compatibility.

Made-with: Cursor
This commit is contained in:
Franz Kafka 2026-03-20 20:34:08 +01:00
parent 7783367c71
commit dc44837219
32 changed files with 3330 additions and 0 deletions

View file

@ -0,0 +1,74 @@
package search
import (
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
)
func TestParseSearchRequest_MissingQ(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/search?format=json", nil)
_, err := ParseSearchRequest(r)
if err == nil {
t.Fatalf("expected error, got nil")
}
}
func TestParseSearchRequest_InvalidPageno(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/search?q=hi&pageno=0", nil)
_, err := ParseSearchRequest(r)
if err == nil {
t.Fatalf("expected error for pageno, got nil")
}
}
func TestParseSearchRequest_InvalidLanguage(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/search?q=hi&language=bad!", nil)
_, err := ParseSearchRequest(r)
if err == nil {
t.Fatalf("expected error for language, got nil")
}
}
func TestParseSearchRequest_CategoriesAndEngineData(t *testing.T) {
values := url.Values{}
values.Set("q", "hello")
values.Set("format", "json")
values.Set("categories", "general,science")
values.Set("category_science", "off")
values.Set("engines", "wikipedia,arxiv")
values.Set("engine_data-wikipedia-timeout", "123")
r := httptest.NewRequest(http.MethodPost, "/search", strings.NewReader(values.Encode()))
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req, err := ParseSearchRequest(r)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// categories should drop `science` due to category_science=off
wantCats := map[string]bool{"general": true}
gotCats := map[string]bool{}
for _, c := range req.Categories {
gotCats[c] = true
}
for c := range wantCats {
if !gotCats[c] {
t.Fatalf("expected category %q in result, got %v", c, req.Categories)
}
}
if gotCats["science"] {
t.Fatalf("expected category science to be removed, got %v", req.Categories)
}
if len(req.Engines) != 2 {
t.Fatalf("expected 2 engines, got %v", req.Engines)
}
if req.EngineData["wikipedia"]["timeout"] != "123" {
t.Fatalf("expected engine_data parsed, got %#v", req.EngineData)
}
}