From 378da4c9e24590d312852ddb268164c9283c5b6f Mon Sep 17 00:00:00 2001 From: Omid Astaraki Date: Mon, 16 Mar 2026 12:04:41 +0000 Subject: [PATCH] fix(search): use raw HTTP request in multi command to avoid broken union-type unmarshal The generated SearchAPI.SearchGet client incorrectly parses TV results as PersonResult due to a union-type unmarshal bug. Switch to apiutil.RawGet so mixed movie/TV/person results are preserved correctly and spaces in queries are encoded as %20, consistent with the MCP implementation. --- cmd/search/multi.go | 25 +++++++++--- tests/search_multi_test.go | 82 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 tests/search_multi_test.go diff --git a/cmd/search/multi.go b/cmd/search/multi.go index dff070c..4560352 100644 --- a/cmd/search/multi.go +++ b/cmd/search/multi.go @@ -1,6 +1,9 @@ package search import ( + "net/url" + "strconv" + "seerr-cli/cmd/apiutil" "github.com/spf13/cobra" @@ -21,16 +24,28 @@ var multiCmd = &cobra.Command{ page, _ := cmd.Flags().GetFloat32("page") language, _ := cmd.Flags().GetString("language") - req := apiClient.SearchAPI.SearchGet(ctx).Query(query) + params := url.Values{} + params.Set("query", query) if cmd.Flags().Changed("page") { - req = req.Page(page) + params.Set("page", strconv.Itoa(int(page))) } if cmd.Flags().Changed("language") { - req = req.Language(language) + params.Set("language", language) + } + + // Use a raw HTTP request to avoid the broken union-type unmarshal in the + // generated client (TV results are incorrectly parsed as PersonResult) and + // to ensure spaces are encoded as %20 rather than + in the query string. + b, err := apiutil.RawGet(ctx, apiClient, "/search", params) + if err != nil { + return err } - res, r, err := req.Execute() - return apiutil.HandleResponse(cmd, r, err, res, isVerbose, "SearchGet") + if isVerbose { + cmd.Printf("GET /api/v1/search\n") + } + cmd.Println(string(b)) + return nil }, } diff --git a/tests/search_multi_test.go b/tests/search_multi_test.go new file mode 100644 index 0000000..0bec8f8 --- /dev/null +++ b/tests/search_multi_test.go @@ -0,0 +1,82 @@ +package tests + +import ( + "bytes" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "seerr-cli/cmd" + "seerr-cli/cmd/apiutil" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" +) + +// TestSearchMultiMixedResults verifies that movie, TV, and person results are +// all preserved correctly in the response — the generated client's broken +// union-type unmarshal would lose TV results by misidentifying them as persons. +func TestSearchMultiMixedResults(t *testing.T) { + mixedResponse := `{"page":1,"totalPages":1,"totalResults":3,"results":[` + + `{"id":1,"mediaType":"movie","title":"The Matrix"},` + + `{"id":2,"mediaType":"tv","name":"Matrix Reloaded"},` + + `{"id":3,"mediaType":"person","name":"Keanu Reeves"}` + + `]}` + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/api/v1/search", r.URL.Path) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, mixedResponse) + })) + defer server.Close() + + viper.Set("seerr.server", server.URL) + viper.Set("seerr.api_key", "test-key") + apiutil.OverrideServerURL = server.URL + "/api/v1" + defer func() { apiutil.OverrideServerURL = "" }() + + buf := new(bytes.Buffer) + cmd.RootCmd.SetOut(buf) + cmd.RootCmd.SetArgs([]string{"search", "multi", "-q", "Matrix"}) + + err := cmd.RootCmd.Execute() + assert.NoError(t, err) + + out := buf.String() + assert.Contains(t, out, `"mediaType":"movie"`) + assert.Contains(t, out, `"mediaType":"tv"`) + assert.Contains(t, out, `"mediaType":"person"`) +} + +// TestSearchMultiQueryEncoding verifies that spaces in the query are encoded +// as %20 and not + in the URL sent to the server. +func TestSearchMultiQueryEncoding(t *testing.T) { + var capturedQuery string + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + capturedQuery = r.URL.RawQuery + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"page":1,"totalResults":0,"results":[]}`) + })) + defer server.Close() + + viper.Set("seerr.server", server.URL) + viper.Set("seerr.api_key", "test-key") + apiutil.OverrideServerURL = server.URL + "/api/v1" + defer func() { apiutil.OverrideServerURL = "" }() + + buf := new(bytes.Buffer) + cmd.RootCmd.SetOut(buf) + cmd.RootCmd.SetArgs([]string{"search", "multi", "-q", "the matrix"}) + + err := cmd.RootCmd.Execute() + assert.NoError(t, err) + + // Spaces must be encoded as %20, not as +. + assert.True(t, strings.Contains(capturedQuery, "%20"), "expected %%20 encoding in query %q", capturedQuery) + assert.False(t, strings.Contains(capturedQuery, "+"), "unexpected + encoding in query %q", capturedQuery) +}