diff --git a/internal/server/server.go b/internal/server/server.go index bfb1255..feb35df 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -716,16 +716,21 @@ func toHomePageSessions(docs []session.DocumentInfo) []homePageSession { func buildClearURL(u *url.URL, removeKey string) string { q := u.Query() q.Del(removeKey) - if len(q) == 0 { - return "/" - } - return "/?" + q.Encode() + return (&url.URL{ + Path: "/", + RawQuery: q.Encode(), + Fragment: u.Fragment, + }).String() } func buildPageURL(u *url.URL, page int) string { q := u.Query() q.Set("page", fmt.Sprintf("%d", page)) - return "/?" + q.Encode() + return (&url.URL{ + Path: "/", + RawQuery: q.Encode(), + Fragment: u.Fragment, + }).String() } func (s *Server) handleCreateSession(w http.ResponseWriter, r *http.Request) { diff --git a/internal/server/server_test.go b/internal/server/server_test.go index 077dee3..d3a3393 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -715,3 +715,133 @@ func TestDownloadSessionNotFound(t *testing.T) { t.Fatalf("expected 404, got %d", resp.StatusCode) } } + +func TestBuildClearURL(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + rawURL string + removeKey string + want string + }{ + { + name: "no query no fragment", + rawURL: "/", + removeKey: "q", + want: "/", + }, + { + name: "remove only param leaves root", + rawURL: "/?q=test", + removeKey: "q", + want: "/", + }, + { + name: "remove one param keeps others", + rawURL: "/?q=test&tag=foo", + removeKey: "q", + want: "/?tag=foo", + }, + { + name: "fragment preserved with no remaining params", + rawURL: "/?q=test#section", + removeKey: "q", + want: "/#section", + }, + { + name: "fragment preserved with remaining params", + rawURL: "/?q=test&tag=foo#section", + removeKey: "q", + want: "/?tag=foo#section", + }, + { + name: "fragment preserved when key not present", + rawURL: "/?tag=foo#section", + removeKey: "q", + want: "/?tag=foo#section", + }, + { + name: "fragment only no query params", + rawURL: "/#section", + removeKey: "q", + want: "/#section", + }, + { + name: "fragment with special characters encoded safely", + rawURL: "/?q=test#sec%20tion", + removeKey: "q", + want: "/#sec%20tion", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + u, err := url.Parse(tt.rawURL) + if err != nil { + t.Fatal(err) + } + got := buildClearURL(u, tt.removeKey) + if got != tt.want { + t.Errorf("buildClearURL(%q, %q) = %q, want %q", tt.rawURL, tt.removeKey, got, tt.want) + } + }) + } +} + +func TestBuildPageURL(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + rawURL string + page int + want string + }{ + { + name: "basic page", + rawURL: "/", + page: 2, + want: "/?page=2", + }, + { + name: "preserves existing params", + rawURL: "/?q=test", + page: 3, + want: "/?page=3&q=test", + }, + { + name: "preserves fragment", + rawURL: "/?q=test#section", + page: 2, + want: "/?page=2&q=test#section", + }, + { + name: "fragment only no query", + rawURL: "/#section", + page: 1, + want: "/?page=1#section", + }, + { + name: "replaces existing page param", + rawURL: "/?page=1&q=test", + page: 5, + want: "/?page=5&q=test", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + u, err := url.Parse(tt.rawURL) + if err != nil { + t.Fatal(err) + } + got := buildPageURL(u, tt.page) + if got != tt.want { + t.Errorf("buildPageURL(%q, %d) = %q, want %q", tt.rawURL, tt.page, got, tt.want) + } + }) + } +}