Skip to content

Commit 1e010d8

Browse files
authored
Fix docs pull to accept full URLs (#357)
1 parent 47c9195 commit 1e010d8

File tree

3 files changed

+178
-1
lines changed

3 files changed

+178
-1
lines changed

internal/cli/service_docs_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,28 @@ func TestService_DocsPull(t *testing.T) {
378378
require.Empty(t, s.mockStdout.String())
379379
})
380380

381+
t.Run("when a full URL is provided", func(t *testing.T) {
382+
s := setupDocsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
383+
require.Equal(t, "/docs/rwx/guides/ci", r.URL.Path)
384+
require.Equal(t, "text/markdown", r.Header.Get("Accept"))
385+
386+
fmt.Fprint(w, "# CI Guide\n\nWelcome to CI.")
387+
}))
388+
389+
// Build a full URL using the test server's address
390+
addr := s.config.DocsClient.Host
391+
fullURL := fmt.Sprintf("http://%s/docs/rwx/guides/ci", addr)
392+
393+
result, err := s.service.DocsPull(cli.DocsPullConfig{
394+
URL: fullURL,
395+
})
396+
397+
require.NoError(t, err)
398+
require.Equal(t, fullURL, result.URL)
399+
require.Contains(t, s.mockStdout.String(), "# CI Guide")
400+
require.Contains(t, s.mockStdout.String(), "Welcome to CI.")
401+
})
402+
381403
t.Run("when the article API returns an error", func(t *testing.T) {
382404
s := setupDocsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
383405
w.WriteHeader(http.StatusNotFound)

internal/docs/client.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net/http"
88
"net/url"
99
"strconv"
10+
"strings"
1011

1112
"github.com/rwx-cloud/cli/cmd/rwx/config"
1213
)
@@ -81,8 +82,28 @@ func (c Client) Search(query string, limit int) (*SearchResponse, error) {
8182
return &result, nil
8283
}
8384

85+
// resolveURL converts a URL or path into a fully qualified request URL.
86+
// It accepts full URLs (with or without scheme), host-prefixed paths, and bare paths.
87+
func (c Client) resolveURL(urlOrPath string) string {
88+
host := c.host()
89+
hostWithoutWWW := strings.TrimPrefix(host, "www.")
90+
91+
// Full URL with scheme: https://www.rwx.com/docs/... or http://rwx.com/docs/...
92+
if strings.HasPrefix(urlOrPath, "https://") || strings.HasPrefix(urlOrPath, "http://") {
93+
return urlOrPath
94+
}
95+
96+
// Host-prefixed without scheme: www.rwx.com/docs/... or rwx.com/docs/...
97+
if strings.HasPrefix(urlOrPath, host) || strings.HasPrefix(urlOrPath, hostWithoutWWW) {
98+
return fmt.Sprintf("%s://%s", c.scheme(), urlOrPath)
99+
}
100+
101+
// Bare path: /docs/rwx/guides/ci
102+
return fmt.Sprintf("%s://%s%s", c.scheme(), host, urlOrPath)
103+
}
104+
84105
func (c Client) FetchArticle(urlOrPath string) (string, error) {
85-
reqURL := fmt.Sprintf("%s://%s%s", c.scheme(), c.host(), urlOrPath)
106+
reqURL := c.resolveURL(urlOrPath)
86107

87108
req, err := http.NewRequest("GET", reqURL, nil)
88109
if err != nil {

internal/docs/client_test.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package docs
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestClient_resolveURL(t *testing.T) {
13+
c := Client{Host: "www.rwx.com", Scheme: "https"}
14+
15+
tests := []struct {
16+
name string
17+
input string
18+
expected string
19+
}{
20+
{
21+
name: "bare path",
22+
input: "/docs/rwx/guides/ci",
23+
expected: "https://www.rwx.com/docs/rwx/guides/ci",
24+
},
25+
{
26+
name: "full URL with https scheme",
27+
input: "https://www.rwx.com/docs/rwx/guides/ci",
28+
expected: "https://www.rwx.com/docs/rwx/guides/ci",
29+
},
30+
{
31+
name: "full URL with http scheme",
32+
input: "http://www.rwx.com/docs/rwx/guides/ci",
33+
expected: "http://www.rwx.com/docs/rwx/guides/ci",
34+
},
35+
{
36+
name: "host with www but no scheme",
37+
input: "www.rwx.com/docs/rwx/guides/ci",
38+
expected: "https://www.rwx.com/docs/rwx/guides/ci",
39+
},
40+
{
41+
name: "host without www or scheme",
42+
input: "rwx.com/docs/rwx/guides/ci",
43+
expected: "https://rwx.com/docs/rwx/guides/ci",
44+
},
45+
}
46+
47+
for _, tt := range tests {
48+
t.Run(tt.name, func(t *testing.T) {
49+
result := c.resolveURL(tt.input)
50+
require.Equal(t, tt.expected, result)
51+
})
52+
}
53+
}
54+
55+
func TestClient_resolveURL_customHost(t *testing.T) {
56+
c := Client{Host: "docs.example.com", Scheme: "http"}
57+
58+
tests := []struct {
59+
name string
60+
input string
61+
expected string
62+
}{
63+
{
64+
name: "bare path with custom host",
65+
input: "/some/article",
66+
expected: "http://docs.example.com/some/article",
67+
},
68+
{
69+
name: "full URL ignores custom host",
70+
input: "https://other.com/some/article",
71+
expected: "https://other.com/some/article",
72+
},
73+
{
74+
name: "custom host without scheme",
75+
input: "docs.example.com/some/article",
76+
expected: "http://docs.example.com/some/article",
77+
},
78+
}
79+
80+
for _, tt := range tests {
81+
t.Run(tt.name, func(t *testing.T) {
82+
result := c.resolveURL(tt.input)
83+
require.Equal(t, tt.expected, result)
84+
})
85+
}
86+
}
87+
88+
func TestClient_FetchArticle_withFullURL(t *testing.T) {
89+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
90+
require.Equal(t, "/docs/rwx/guides/ci", r.URL.Path)
91+
require.Equal(t, "text/markdown", r.Header.Get("Accept"))
92+
fmt.Fprint(w, "# CI Guide\n\nWelcome.")
93+
}))
94+
t.Cleanup(server.Close)
95+
96+
c := Client{Host: server.Listener.Addr().String(), Scheme: "http"}
97+
98+
// Pass a full URL pointing at the test server
99+
fullURL := fmt.Sprintf("http://%s/docs/rwx/guides/ci", server.Listener.Addr().String())
100+
body, err := c.FetchArticle(fullURL)
101+
require.NoError(t, err)
102+
require.Equal(t, "# CI Guide\n\nWelcome.", body)
103+
}
104+
105+
func TestClient_FetchArticle_withPath(t *testing.T) {
106+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
107+
require.Equal(t, "/docs/rwx/guides/ci", r.URL.Path)
108+
require.Equal(t, "text/markdown", r.Header.Get("Accept"))
109+
fmt.Fprint(w, "# CI Guide\n\nWelcome.")
110+
}))
111+
t.Cleanup(server.Close)
112+
113+
c := Client{Host: server.Listener.Addr().String(), Scheme: "http"}
114+
115+
body, err := c.FetchArticle("/docs/rwx/guides/ci")
116+
require.NoError(t, err)
117+
require.Equal(t, "# CI Guide\n\nWelcome.", body)
118+
}
119+
120+
func TestClient_FetchArticle_withHostNoScheme(t *testing.T) {
121+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
122+
require.Equal(t, "/docs/rwx/guides/ci", r.URL.Path)
123+
fmt.Fprint(w, "# CI Guide\n\nWelcome.")
124+
}))
125+
t.Cleanup(server.Close)
126+
127+
c := Client{Host: server.Listener.Addr().String(), Scheme: "http"}
128+
129+
// Pass host-prefixed input (without scheme)
130+
hostPrefixed := fmt.Sprintf("%s/docs/rwx/guides/ci", server.Listener.Addr().String())
131+
body, err := c.FetchArticle(hostPrefixed)
132+
require.NoError(t, err)
133+
require.Equal(t, "# CI Guide\n\nWelcome.", body)
134+
}

0 commit comments

Comments
 (0)