Skip to content

Commit a334bab

Browse files
authored
Set user-agent for client requests (#72)
1 parent d1d53a8 commit a334bab

File tree

4 files changed

+237
-3
lines changed

4 files changed

+237
-3
lines changed

internal/tiger/api/client_util.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ func NewTigerClient(apiKey string) (*ClientWithResponses, error) {
5353
// Add API key to Authorization header
5454
encodedKey := base64.StdEncoding.EncodeToString([]byte(apiKey))
5555
req.Header.Set("Authorization", "Basic "+encodedKey)
56+
// Add User-Agent header to identify CLI version
57+
req.Header.Set("User-Agent", fmt.Sprintf("tiger-cli/%s", config.Version))
5658
return nil
5759
}))
5860

internal/tiger/api/client_util_test.go

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ package api_test
33
import (
44
"context"
55
"net/http"
6+
"net/http/httptest"
67
"testing"
78

8-
"github.com/timescale/tiger-cli/internal/tiger/util"
9-
"go.uber.org/mock/gomock"
10-
119
"github.com/timescale/tiger-cli/internal/tiger/api"
1210
"github.com/timescale/tiger-cli/internal/tiger/api/mocks"
11+
"github.com/timescale/tiger-cli/internal/tiger/config"
12+
"github.com/timescale/tiger-cli/internal/tiger/util"
13+
"go.uber.org/mock/gomock"
1314
)
1415

1516
func TestValidateAPIKeyWithClient(t *testing.T) {
@@ -121,3 +122,98 @@ func TestValidateAPIKey_Integration(t *testing.T) {
121122
// This test would require a real API key and network connectivity
122123
t.Skip("Integration test requires real API key - implement when needed")
123124
}
125+
126+
func TestNewTigerClientUserAgent(t *testing.T) {
127+
// Create a test server that captures the User-Agent header
128+
var capturedUserAgent string
129+
var requestReceived bool
130+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
131+
requestReceived = true
132+
capturedUserAgent = r.Header.Get("User-Agent")
133+
// Return a valid JSON response
134+
w.Header().Set("Content-Type", "application/json")
135+
w.WriteHeader(http.StatusOK)
136+
w.Write([]byte(`[]`)) // Empty array for services list
137+
}))
138+
defer server.Close()
139+
140+
// Setup test config with the test server URL
141+
_, err := config.UseTestConfig(t.TempDir(), map[string]any{
142+
"api_url": server.URL,
143+
})
144+
if err != nil {
145+
t.Fatalf("Failed to setup test config: %v", err)
146+
}
147+
148+
// Create a new Tiger client
149+
client, err := api.NewTigerClient("test-api-key")
150+
if err != nil {
151+
t.Fatalf("Failed to create Tiger client: %v", err)
152+
}
153+
154+
// Make a request to trigger the User-Agent header
155+
ctx := context.Background()
156+
_, err = client.GetProjectsProjectIdServicesWithResponse(ctx, "test-project-id")
157+
if err != nil {
158+
t.Fatalf("Request failed: %v", err)
159+
}
160+
161+
if !requestReceived {
162+
t.Fatal("Request was not received by test server")
163+
}
164+
165+
// Verify the User-Agent header was set correctly
166+
expectedUserAgent := "tiger-cli/" + config.Version
167+
if capturedUserAgent != expectedUserAgent {
168+
t.Errorf("Expected User-Agent %q, got %q", expectedUserAgent, capturedUserAgent)
169+
}
170+
}
171+
172+
func TestNewTigerClientAuthorizationHeader(t *testing.T) {
173+
// Create a test server that captures the Authorization header
174+
var capturedAuthHeader string
175+
var requestReceived bool
176+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
177+
requestReceived = true
178+
capturedAuthHeader = r.Header.Get("Authorization")
179+
// Return a valid JSON response
180+
w.Header().Set("Content-Type", "application/json")
181+
w.WriteHeader(http.StatusOK)
182+
w.Write([]byte(`[]`)) // Empty array for services list
183+
}))
184+
defer server.Close()
185+
186+
// Setup test config with the test server URL
187+
_, err := config.UseTestConfig(t.TempDir(), map[string]any{
188+
"api_url": server.URL,
189+
})
190+
if err != nil {
191+
t.Fatalf("Failed to setup test config: %v", err)
192+
}
193+
194+
// Create a new Tiger client with a test API key
195+
apiKey := "test-api-key:test-secret-key"
196+
client, err := api.NewTigerClient(apiKey)
197+
if err != nil {
198+
t.Fatalf("Failed to create Tiger client: %v", err)
199+
}
200+
201+
// Make a request to trigger the Authorization header
202+
ctx := context.Background()
203+
_, err = client.GetProjectsProjectIdServicesWithResponse(ctx, "test-project-id")
204+
if err != nil {
205+
t.Fatalf("Request failed: %v", err)
206+
}
207+
208+
if !requestReceived {
209+
t.Fatal("Request was not received by test server")
210+
}
211+
212+
// Verify the Authorization header was set correctly (should be Base64 encoded)
213+
if capturedAuthHeader == "" {
214+
t.Error("Expected Authorization header to be set, but it was empty")
215+
}
216+
if len(capturedAuthHeader) < 6 || capturedAuthHeader[:6] != "Basic " {
217+
t.Errorf("Expected Authorization header to start with 'Basic ', got: %s", capturedAuthHeader)
218+
}
219+
}

internal/tiger/cmd/graphql.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"io"
77
"net/http"
88
"strings"
9+
10+
"github.com/timescale/tiger-cli/internal/tiger/config"
911
)
1012

1113
// We currently use a few GraphQL endpoints as part of the OAuth login flow,
@@ -151,6 +153,7 @@ func makeGraphQLRequest[T any](queryURL, accessToken, query string, variables ma
151153

152154
req.Header.Set("Content-Type", "application/json")
153155
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
156+
req.Header.Set("User-Agent", fmt.Sprintf("tiger-cli/%s", config.Version))
154157

155158
resp, err := http.DefaultClient.Do(req)
156159
if err != nil {

internal/tiger/cmd/graphql_test.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package cmd
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
9+
"github.com/timescale/tiger-cli/internal/tiger/config"
10+
)
11+
12+
func TestGraphQLUserAgent(t *testing.T) {
13+
// Set up a test server that captures the User-Agent header
14+
var capturedUserAgent string
15+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
16+
capturedUserAgent = r.Header.Get("User-Agent")
17+
18+
// Return a valid GraphQL response
19+
response := GraphQLResponse[GetUserData]{
20+
Data: &GetUserData{
21+
GetUser: User{
22+
ID: "test-user-id",
23+
Name: "Test User",
24+
Email: "test@example.com",
25+
},
26+
},
27+
}
28+
w.Header().Set("Content-Type", "application/json")
29+
w.WriteHeader(http.StatusOK)
30+
json.NewEncoder(w).Encode(response)
31+
}))
32+
defer server.Close()
33+
34+
// Create a GraphQL client pointing to our test server
35+
client := &GraphQLClient{
36+
URL: server.URL,
37+
}
38+
39+
// Make a request
40+
_, err := client.getUser("test-access-token")
41+
if err != nil {
42+
t.Fatalf("Expected no error, got: %v", err)
43+
}
44+
45+
// Verify the User-Agent header was set correctly
46+
expectedUserAgent := "tiger-cli/" + config.Version
47+
if capturedUserAgent != expectedUserAgent {
48+
t.Errorf("Expected User-Agent %q, got %q", expectedUserAgent, capturedUserAgent)
49+
}
50+
}
51+
52+
func TestGraphQLUserAgentInAllRequests(t *testing.T) {
53+
tests := []struct {
54+
name string
55+
requestFunc func(*GraphQLClient, string) (interface{}, error)
56+
}{
57+
{
58+
name: "getUserProjects",
59+
requestFunc: func(c *GraphQLClient, token string) (interface{}, error) {
60+
return c.getUserProjects(token)
61+
},
62+
},
63+
{
64+
name: "getUser",
65+
requestFunc: func(c *GraphQLClient, token string) (interface{}, error) {
66+
return c.getUser(token)
67+
},
68+
},
69+
{
70+
name: "createPATRecord",
71+
requestFunc: func(c *GraphQLClient, token string) (interface{}, error) {
72+
return c.createPATRecord(token, "test-project-id", "test-pat-name")
73+
},
74+
},
75+
}
76+
77+
for _, tt := range tests {
78+
t.Run(tt.name, func(t *testing.T) {
79+
var capturedUserAgent string
80+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
81+
capturedUserAgent = r.Header.Get("User-Agent")
82+
83+
// Return appropriate response based on the query
84+
var response interface{}
85+
if tt.name == "getUserProjects" {
86+
response = GraphQLResponse[GetAllProjectsData]{
87+
Data: &GetAllProjectsData{
88+
GetAllProjects: []Project{
89+
{ID: "test-id", Name: "test-project"},
90+
},
91+
},
92+
}
93+
} else if tt.name == "getUser" {
94+
response = GraphQLResponse[GetUserData]{
95+
Data: &GetUserData{
96+
GetUser: User{ID: "test-id", Name: "Test User", Email: "test@example.com"},
97+
},
98+
}
99+
} else if tt.name == "createPATRecord" {
100+
response = GraphQLResponse[CreatePATRecordData]{
101+
Data: &CreatePATRecordData{
102+
CreatePATRecord: PATRecordResponse{
103+
ClientCredentials: struct {
104+
AccessKey string `json:"accessKey"`
105+
SecretKey string `json:"secretKey"`
106+
}{
107+
AccessKey: "test-access-key",
108+
SecretKey: "test-secret-key",
109+
},
110+
},
111+
},
112+
}
113+
}
114+
115+
w.Header().Set("Content-Type", "application/json")
116+
w.WriteHeader(http.StatusOK)
117+
json.NewEncoder(w).Encode(response)
118+
}))
119+
defer server.Close()
120+
121+
client := &GraphQLClient{URL: server.URL}
122+
_, err := tt.requestFunc(client, "test-token")
123+
if err != nil {
124+
t.Fatalf("Expected no error, got: %v", err)
125+
}
126+
127+
expectedUserAgent := "tiger-cli/" + config.Version
128+
if capturedUserAgent != expectedUserAgent {
129+
t.Errorf("Expected User-Agent %q, got %q", expectedUserAgent, capturedUserAgent)
130+
}
131+
})
132+
}
133+
}

0 commit comments

Comments
 (0)