Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions internal/tiger/api/client_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ func NewTigerClient(apiKey string) (*ClientWithResponses, error) {
// Add API key to Authorization header
encodedKey := base64.StdEncoding.EncodeToString([]byte(apiKey))
req.Header.Set("Authorization", "Basic "+encodedKey)
// Add User-Agent header to identify CLI version
req.Header.Set("User-Agent", fmt.Sprintf("tiger-cli/%s", config.Version))
return nil
}))

Expand Down
102 changes: 99 additions & 3 deletions internal/tiger/api/client_util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package api_test
import (
"context"
"net/http"
"net/http/httptest"
"testing"

"github.com/timescale/tiger-cli/internal/tiger/util"
"go.uber.org/mock/gomock"

"github.com/timescale/tiger-cli/internal/tiger/api"
"github.com/timescale/tiger-cli/internal/tiger/api/mocks"
"github.com/timescale/tiger-cli/internal/tiger/config"
"github.com/timescale/tiger-cli/internal/tiger/util"
"go.uber.org/mock/gomock"
)

func TestValidateAPIKeyWithClient(t *testing.T) {
Expand Down Expand Up @@ -121,3 +122,98 @@ func TestValidateAPIKey_Integration(t *testing.T) {
// This test would require a real API key and network connectivity
t.Skip("Integration test requires real API key - implement when needed")
}

func TestNewTigerClientUserAgent(t *testing.T) {
// Create a test server that captures the User-Agent header
var capturedUserAgent string
var requestReceived bool
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestReceived = true
capturedUserAgent = r.Header.Get("User-Agent")
// Return a valid JSON response
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`[]`)) // Empty array for services list
}))
defer server.Close()

// Setup test config with the test server URL
_, err := config.UseTestConfig(t.TempDir(), map[string]any{
"api_url": server.URL,
})
if err != nil {
t.Fatalf("Failed to setup test config: %v", err)
}

// Create a new Tiger client
client, err := api.NewTigerClient("test-api-key")
if err != nil {
t.Fatalf("Failed to create Tiger client: %v", err)
}

// Make a request to trigger the User-Agent header
ctx := context.Background()
_, err = client.GetProjectsProjectIdServicesWithResponse(ctx, "test-project-id")
if err != nil {
t.Fatalf("Request failed: %v", err)
}

if !requestReceived {
t.Fatal("Request was not received by test server")
}

// Verify the User-Agent header was set correctly
expectedUserAgent := "tiger-cli/" + config.Version
if capturedUserAgent != expectedUserAgent {
t.Errorf("Expected User-Agent %q, got %q", expectedUserAgent, capturedUserAgent)
}
}

func TestNewTigerClientAuthorizationHeader(t *testing.T) {
// Create a test server that captures the Authorization header
var capturedAuthHeader string
var requestReceived bool
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestReceived = true
capturedAuthHeader = r.Header.Get("Authorization")
// Return a valid JSON response
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`[]`)) // Empty array for services list
}))
defer server.Close()

// Setup test config with the test server URL
_, err := config.UseTestConfig(t.TempDir(), map[string]any{
"api_url": server.URL,
})
if err != nil {
t.Fatalf("Failed to setup test config: %v", err)
}

// Create a new Tiger client with a test API key
apiKey := "test-api-key:test-secret-key"
client, err := api.NewTigerClient(apiKey)
if err != nil {
t.Fatalf("Failed to create Tiger client: %v", err)
}

// Make a request to trigger the Authorization header
ctx := context.Background()
_, err = client.GetProjectsProjectIdServicesWithResponse(ctx, "test-project-id")
if err != nil {
t.Fatalf("Request failed: %v", err)
}

if !requestReceived {
t.Fatal("Request was not received by test server")
}

// Verify the Authorization header was set correctly (should be Base64 encoded)
if capturedAuthHeader == "" {
t.Error("Expected Authorization header to be set, but it was empty")
}
if len(capturedAuthHeader) < 6 || capturedAuthHeader[:6] != "Basic " {
t.Errorf("Expected Authorization header to start with 'Basic ', got: %s", capturedAuthHeader)
}
}
3 changes: 3 additions & 0 deletions internal/tiger/cmd/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"io"
"net/http"
"strings"

"github.com/timescale/tiger-cli/internal/tiger/config"
)

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

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

resp, err := http.DefaultClient.Do(req)
if err != nil {
Expand Down
133 changes: 133 additions & 0 deletions internal/tiger/cmd/graphql_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package cmd

import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"github.com/timescale/tiger-cli/internal/tiger/config"
)

func TestGraphQLUserAgent(t *testing.T) {
// Set up a test server that captures the User-Agent header
var capturedUserAgent string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
capturedUserAgent = r.Header.Get("User-Agent")

// Return a valid GraphQL response
response := GraphQLResponse[GetUserData]{
Data: &GetUserData{
GetUser: User{
ID: "test-user-id",
Name: "Test User",
Email: "test@example.com",
},
},
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(response)
}))
defer server.Close()

// Create a GraphQL client pointing to our test server
client := &GraphQLClient{
URL: server.URL,
}

// Make a request
_, err := client.getUser("test-access-token")
if err != nil {
t.Fatalf("Expected no error, got: %v", err)
}

// Verify the User-Agent header was set correctly
expectedUserAgent := "tiger-cli/" + config.Version
if capturedUserAgent != expectedUserAgent {
t.Errorf("Expected User-Agent %q, got %q", expectedUserAgent, capturedUserAgent)
}
}

func TestGraphQLUserAgentInAllRequests(t *testing.T) {
tests := []struct {
name string
requestFunc func(*GraphQLClient, string) (interface{}, error)
}{
{
name: "getUserProjects",
requestFunc: func(c *GraphQLClient, token string) (interface{}, error) {
return c.getUserProjects(token)
},
},
{
name: "getUser",
requestFunc: func(c *GraphQLClient, token string) (interface{}, error) {
return c.getUser(token)
},
},
{
name: "createPATRecord",
requestFunc: func(c *GraphQLClient, token string) (interface{}, error) {
return c.createPATRecord(token, "test-project-id", "test-pat-name")
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var capturedUserAgent string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
capturedUserAgent = r.Header.Get("User-Agent")

// Return appropriate response based on the query
var response interface{}
if tt.name == "getUserProjects" {
response = GraphQLResponse[GetAllProjectsData]{
Data: &GetAllProjectsData{
GetAllProjects: []Project{
{ID: "test-id", Name: "test-project"},
},
},
}
} else if tt.name == "getUser" {
response = GraphQLResponse[GetUserData]{
Data: &GetUserData{
GetUser: User{ID: "test-id", Name: "Test User", Email: "test@example.com"},
},
}
} else if tt.name == "createPATRecord" {
response = GraphQLResponse[CreatePATRecordData]{
Data: &CreatePATRecordData{
CreatePATRecord: PATRecordResponse{
ClientCredentials: struct {
AccessKey string `json:"accessKey"`
SecretKey string `json:"secretKey"`
}{
AccessKey: "test-access-key",
SecretKey: "test-secret-key",
},
},
},
}
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(response)
}))
defer server.Close()

client := &GraphQLClient{URL: server.URL}
_, err := tt.requestFunc(client, "test-token")
if err != nil {
t.Fatalf("Expected no error, got: %v", err)
}

expectedUserAgent := "tiger-cli/" + config.Version
if capturedUserAgent != expectedUserAgent {
t.Errorf("Expected User-Agent %q, got %q", expectedUserAgent, capturedUserAgent)
}
})
}
}