124 lines
4.1 KiB
Go
124 lines
4.1 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/Silo-Server/silo-server/internal/catalog"
|
|
)
|
|
|
|
// decodeCatalogResponse runs writeCatalogResponse against an httptest recorder
|
|
// and returns the decoded JSON body as a generic map so individual keys can be
|
|
// asserted for presence/absence (search_diagnostics is omitempty).
|
|
func decodeCatalogResponse(t *testing.T, result *catalog.CatalogResult, grouped bool) map[string]any {
|
|
t.Helper()
|
|
rec := httptest.NewRecorder()
|
|
// writeCatalogResponse uses no handler state, so a zero-value handler is fine.
|
|
(&CatalogHandler{}).writeCatalogResponse(rec, result, []itemListResponse{}, grouped)
|
|
|
|
var body map[string]any
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
|
t.Fatalf("decoding catalog response: %v\nbody: %s", err, rec.Body.String())
|
|
}
|
|
return body
|
|
}
|
|
|
|
func TestWriteCatalogResponse_DiagnosticsKeywordFallback(t *testing.T) {
|
|
body := decodeCatalogResponse(t, &catalog.CatalogResult{
|
|
Total: 3,
|
|
TotalExact: true,
|
|
HasMore: false,
|
|
Provider: catalog.SearchProviderMeilisearch,
|
|
Mode: "keyword",
|
|
SemanticUsed: false,
|
|
FallbackReason: `semantic_not_ready: type "movie" coverage 40% below threshold`,
|
|
}, false)
|
|
|
|
// Existing keys must stay present and unchanged (byte-stable contract).
|
|
for _, key := range []string{"total", "total_exact", "has_more", "items"} {
|
|
if _, ok := body[key]; !ok {
|
|
t.Fatalf("response missing existing key %q: %v", key, body)
|
|
}
|
|
}
|
|
if body["total"].(float64) != 3 {
|
|
t.Fatalf("total = %v, want 3", body["total"])
|
|
}
|
|
if body["total_exact"].(bool) != true {
|
|
t.Fatalf("total_exact = %v, want true", body["total_exact"])
|
|
}
|
|
|
|
diagRaw, ok := body["search_diagnostics"]
|
|
if !ok {
|
|
t.Fatalf("expected search_diagnostics in response: %v", body)
|
|
}
|
|
diag := diagRaw.(map[string]any)
|
|
if diag["provider"] != catalog.SearchProviderMeilisearch {
|
|
t.Fatalf("provider = %v, want %q", diag["provider"], catalog.SearchProviderMeilisearch)
|
|
}
|
|
if diag["mode"] != "keyword" {
|
|
t.Fatalf("mode = %v, want keyword", diag["mode"])
|
|
}
|
|
if diag["semantic_used"].(bool) != false {
|
|
t.Fatalf("semantic_used = %v, want false", diag["semantic_used"])
|
|
}
|
|
if diag["fallback_reason"] != `semantic_not_ready: type "movie" coverage 40% below threshold` {
|
|
t.Fatalf("fallback_reason = %v", diag["fallback_reason"])
|
|
}
|
|
}
|
|
|
|
func TestWriteCatalogResponse_DiagnosticsHybridOmitsFallbackReason(t *testing.T) {
|
|
body := decodeCatalogResponse(t, &catalog.CatalogResult{
|
|
Provider: catalog.SearchProviderMeilisearch,
|
|
Mode: "hybrid",
|
|
SemanticUsed: true,
|
|
IndexPendingEvents: 7,
|
|
}, false)
|
|
|
|
diagRaw, ok := body["search_diagnostics"]
|
|
if !ok {
|
|
t.Fatalf("expected search_diagnostics in response: %v", body)
|
|
}
|
|
diag := diagRaw.(map[string]any)
|
|
if diag["semantic_used"].(bool) != true {
|
|
t.Fatalf("semantic_used = %v, want true", diag["semantic_used"])
|
|
}
|
|
if diag["mode"] != "hybrid" {
|
|
t.Fatalf("mode = %v, want hybrid", diag["mode"])
|
|
}
|
|
if _, ok := diag["fallback_reason"]; ok {
|
|
t.Fatalf("fallback_reason should be omitted when empty: %v", diag)
|
|
}
|
|
if diag["index_pending_updates"].(float64) != 7 {
|
|
t.Fatalf("index_pending_updates = %v, want 7", diag["index_pending_updates"])
|
|
}
|
|
}
|
|
|
|
func TestWriteCatalogResponse_NoProviderOmitsDiagnostics(t *testing.T) {
|
|
// Browse / preview / non-relevance q= paths never set Provider.
|
|
body := decodeCatalogResponse(t, &catalog.CatalogResult{
|
|
Total: 5,
|
|
TotalExact: true,
|
|
}, false)
|
|
|
|
if _, ok := body["search_diagnostics"]; ok {
|
|
t.Fatalf("search_diagnostics should be omitted when no provider search ran: %v", body)
|
|
}
|
|
}
|
|
|
|
func TestWriteCatalogResponse_GroupedByWorkOmitsDiagnostics(t *testing.T) {
|
|
// group=work builds a fresh CatalogResult with an empty Provider.
|
|
body := decodeCatalogResponse(t, &catalog.CatalogResult{
|
|
Total: 2,
|
|
TotalExact: true,
|
|
}, true)
|
|
|
|
if _, ok := body["search_diagnostics"]; ok {
|
|
t.Fatalf("grouped response should omit search_diagnostics: %v", body)
|
|
}
|
|
// Grouped responses force total_exact false regardless of result.TotalExact.
|
|
if body["total_exact"].(bool) != false {
|
|
t.Fatalf("grouped total_exact = %v, want false", body["total_exact"])
|
|
}
|
|
}
|