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
10 changes: 8 additions & 2 deletions internal/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ func WithCircuitBreaker(cb *CircuitBreaker) NotteClientOption {

// NewClient creates a new Notte API client
func NewClient(apiKey string, opts ...NotteClientOption) (*NotteClient, error) {
return NewClientWithURL(apiKey, DefaultBaseURL, opts...)
return NewClientWithURL(apiKey, DefaultBaseURL, "", opts...)
}

// NewClientWithURL creates a client with custom base URL
func NewClientWithURL(apiKey, baseURL string, opts ...NotteClientOption) (*NotteClient, error) {
func NewClientWithURL(apiKey, baseURL, version string, opts ...NotteClientOption) (*NotteClient, error) {
if apiKey == "" {
return nil, fmt.Errorf("API key is required")
}
Expand All @@ -67,6 +67,7 @@ func NewClientWithURL(apiKey, baseURL string, opts ...NotteClientOption) (*Notte
Timeout: 45 * time.Second,
Transport: &resilientTransport{
apiKey: apiKey,
version: version,
retryConfig: nc.retryConfig,
circuitBreaker: nc.circuitBreaker,
base: &http.Transport{
Expand All @@ -92,6 +93,7 @@ func NewClientWithURL(apiKey, baseURL string, opts ...NotteClientOption) (*Notte
// resilientTransport wraps http.RoundTripper with auth, retry, and circuit breaker
type resilientTransport struct {
apiKey string
version string
retryConfig *RetryConfig
circuitBreaker *CircuitBreaker
base http.RoundTripper
Expand All @@ -108,6 +110,10 @@ func (t *resilientTransport) RoundTrip(req *http.Request) (*http.Response, error
// Add auth header
req.Header.Set("Authorization", "Bearer "+t.apiKey)

// Add tracking headers
req.Header.Set("x-notte-request-origin", "cli")
req.Header.Set("x-notte-sdk-version", t.version)

// Add idempotency key for mutating requests
AddIdempotencyKey(req)

Expand Down
38 changes: 35 additions & 3 deletions internal/api/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func TestNewClient_Success(t *testing.T) {
}

func TestNewClientWithURL_CustomURL(t *testing.T) {
client, err := NewClientWithURL("test-key", "https://custom.api.com")
client, err := NewClientWithURL("test-key", "https://custom.api.com", "v1.0.0")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
Expand Down Expand Up @@ -70,7 +70,39 @@ func TestResilientTransport_AddsAuthHeader(t *testing.T) {
}))
defer server.Close()

client, err := NewClientWithURL("test-api-key", server.URL)
client, err := NewClientWithURL("test-api-key", server.URL, "v1.0.0")
if err != nil {
t.Fatalf("failed to create client: %v", err)
}

req, _ := http.NewRequest("GET", server.URL+"/test", nil)
resp, err := client.httpClient.Do(req)
if err != nil {
t.Fatalf("request failed: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
t.Errorf("got status %d, want 200", resp.StatusCode)
}
}

func TestResilientTransport_AddsTrackingHeaders(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("x-notte-request-origin")
if origin != "cli" {
t.Errorf("got x-notte-request-origin %q, want %q", origin, "cli")
}
version := r.Header.Get("x-notte-sdk-version")
if version != "v1.2.3" {
t.Errorf("got x-notte-sdk-version %q, want %q", version, "v1.2.3")
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{}`))
}))
defer server.Close()

client, err := NewClientWithURL("test-api-key", server.URL, "v1.2.3")
if err != nil {
t.Fatalf("failed to create client: %v", err)
}
Expand Down Expand Up @@ -101,7 +133,7 @@ func TestResilientTransport_RecordsFailureOn5xx(t *testing.T) {
MaxBackoff: 10 * time.Millisecond,
Jitter: false,
}
client, err := NewClientWithURL("test-key", server.URL,
client, err := NewClientWithURL("test-key", server.URL, "v1.0.0",
WithCircuitBreaker(cb),
WithRetryConfig(fastRetry))
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func GetClient() (*api.NotteClient, error) {
baseURL = api.DefaultBaseURL
}

return api.NewClientWithURL(apiKey, baseURL)
return api.NewClientWithURL(apiKey, baseURL, Version)
}

// GetContextWithTimeout wraps the provided context with a timeout
Expand Down