// samsa — 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 httpapi_test import ( "encoding/json" "io" "net/http" "net/http/httptest" "strings" "testing" "github.com/metamorphosis-dev/samsa/internal/contracts" "github.com/metamorphosis-dev/samsa/internal/httpapi" "github.com/metamorphosis-dev/samsa/internal/search" ) // mockUpstreamHandler returns controlled JSON responses. func mockUpstreamJSON(query string) contracts.SearchResponse { return contracts.SearchResponse{ Query: query, NumberOfResults: 2, Results: []contracts.MainResult{ {Title: "Upstream Result 1", URL: ptr("https://upstream.example/1"), Content: "From upstream", Engine: "upstream"}, {Title: "Upstream Result 2", URL: ptr("https://upstream.example/2"), Content: "From upstream", Engine: "upstream"}, }, Answers: []map[string]any{}, Corrections: []string{}, Infoboxes: []map[string]any{}, Suggestions: []string{"upstream suggestion"}, UnresponsiveEngines: [][2]string{}, } } func ptr(s string) *string { return &s } func newTestServer(t *testing.T) (*httptest.Server, *httpapi.Handler) { t.Helper() // Mock upstream server that returns controlled JSON. upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { query := r.FormValue("q") resp := mockUpstreamJSON(query) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(resp) })) t.Cleanup(upstream.Close) svc := search.NewService(search.ServiceConfig{ UpstreamURL: upstream.URL, HTTPTimeout: 0, Cache: nil, EnginesConfig: nil, }) h := httpapi.NewHandler(svc, nil, "https://src.example.com", nil) mux := http.NewServeMux() mux.HandleFunc("/healthz", h.Healthz) mux.HandleFunc("/", h.Index) mux.HandleFunc("/search", h.Search) mux.HandleFunc("/autocompleter", h.Autocompleter) server := httptest.NewServer(mux) t.Cleanup(server.Close) return server, h } func TestHealthz(t *testing.T) { server, _ := newTestServer(t) resp, err := http.Get(server.URL + "/healthz") if err != nil { t.Fatalf("request failed: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("expected status 200, got %d", resp.StatusCode) } if ct := resp.Header.Get("Content-Type"); !strings.Contains(ct, "text/plain") { t.Errorf("expected text/plain, got %s", ct) } } func TestIndex(t *testing.T) { server, _ := newTestServer(t) resp, err := http.Get(server.URL + "/") if err != nil { t.Fatalf("request failed: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("expected status 200, got %d", resp.StatusCode) } if ct := resp.Header.Get("Content-Type"); !strings.Contains(ct, "text/html") { t.Errorf("expected text/html, got %s", ct) } body, _ := io.ReadAll(resp.Body) if !strings.Contains(string(body), "