Skip to content

Commit 1ad6f44

Browse files
Merge branch 'main' into nathan/service-get
2 parents 20d7fbf + 570c06d commit 1ad6f44

File tree

11 files changed

+427
-656
lines changed

11 files changed

+427
-656
lines changed

internal/tiger/api/client.go

Lines changed: 120 additions & 232 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/tiger/api/client_util.go

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package api
33
import (
44
"context"
55
"encoding/base64"
6+
"errors"
67
"fmt"
78
"net/http"
89
"sync"
@@ -92,16 +93,29 @@ func ValidateAPIKeyWithClient(client ClientWithResponsesInterface, projectID str
9293
}
9394

9495
// Check the response status
95-
switch resp.StatusCode() {
96-
case 401, 403:
97-
return fmt.Errorf("invalid API key: authentication failed")
98-
case 404:
99-
// Project not found is OK - it means the API key is valid but project doesn't exist
100-
return nil
101-
case 200:
102-
// Success - API key is valid
96+
if resp.StatusCode() != 200 {
97+
if resp.StatusCode() == 404 {
98+
// Project not found, but API key is valid
99+
return nil
100+
}
101+
if resp.JSON4XX != nil {
102+
return resp.JSON4XX
103+
} else {
104+
return errors.New("unexpected API response: 500")
105+
}
106+
} else {
103107
return nil
104-
default:
105-
return fmt.Errorf("unexpected API response: %d", resp.StatusCode())
106108
}
107109
}
110+
111+
// Error implements the error interface for the Error type.
112+
// This allows Error values to be used directly as Go errors.
113+
func (e *Error) Error() string {
114+
if e == nil {
115+
return "unknown error"
116+
}
117+
if e.Message != nil && *e.Message != "" {
118+
return *e.Message
119+
}
120+
return "unknown error"
121+
}

internal/tiger/api/client_util_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net/http"
66
"testing"
77

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

1011
"github.com/timescale/tiger-cli/internal/tiger/api"
@@ -46,9 +47,10 @@ func TestValidateAPIKeyWithClient(t *testing.T) {
4647
GetProjectsProjectIdServicesWithResponse(gomock.Any(), "00000000-0000-0000-0000-000000000000").
4748
Return(&api.GetProjectsProjectIdServicesResponse{
4849
HTTPResponse: &http.Response{StatusCode: 401},
50+
JSON4XX: &api.ClientError{Message: util.Ptr("Invalid or missing authentication credentials")},
4951
}, nil)
5052
},
51-
expectedError: "invalid API key: authentication failed",
53+
expectedError: "Invalid or missing authentication credentials",
5254
},
5355
{
5456
name: "invalid API key - 403 response",
@@ -57,9 +59,10 @@ func TestValidateAPIKeyWithClient(t *testing.T) {
5759
GetProjectsProjectIdServicesWithResponse(gomock.Any(), "00000000-0000-0000-0000-000000000000").
5860
Return(&api.GetProjectsProjectIdServicesResponse{
5961
HTTPResponse: &http.Response{StatusCode: 403},
62+
JSON4XX: &api.ClientError{Message: util.Ptr("Invalid or missing authentication credentials")},
6063
}, nil)
6164
},
62-
expectedError: "invalid API key: authentication failed",
65+
expectedError: "Invalid or missing authentication credentials",
6366
},
6467
{
6568
name: "unexpected response - 500",

internal/tiger/api/types.go

Lines changed: 2 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/tiger/cmd/db.go

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -321,23 +321,15 @@ func getServiceDetails(cmd *cobra.Command, args []string) (api.Service, error) {
321321
}
322322

323323
// Handle API response
324-
switch resp.StatusCode() {
325-
case 200:
326-
if resp.JSON200 == nil {
327-
return api.Service{}, fmt.Errorf("empty response from API")
328-
}
329-
330-
return *resp.JSON200, nil
324+
if resp.StatusCode() != 200 {
325+
return api.Service{}, exitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX)
326+
}
331327

332-
case 401:
333-
return api.Service{}, exitWithCode(ExitAuthenticationError, fmt.Errorf("authentication failed: invalid API key"))
334-
case 403:
335-
return api.Service{}, exitWithCode(ExitPermissionDenied, fmt.Errorf("permission denied: insufficient access to service"))
336-
case 404:
337-
return api.Service{}, exitWithCode(ExitServiceNotFound, fmt.Errorf("service '%s' not found in project '%s'", serviceID, projectID))
338-
default:
339-
return api.Service{}, fmt.Errorf("API request failed with status %d", resp.StatusCode())
328+
if resp.JSON200 == nil {
329+
return api.Service{}, fmt.Errorf("empty response from API")
340330
}
331+
332+
return *resp.JSON200, nil
341333
}
342334

343335
// ArgsLenAtDashProvider defines the interface for getting ArgsLenAtDash

internal/tiger/cmd/errors.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package cmd
22

3+
import "errors"
4+
35
// Exit codes as defined in the CLI specification
46
const (
57
ExitSuccess = 0 // Success
@@ -32,3 +34,34 @@ func (e exitCodeError) ExitCode() int {
3234
func exitWithCode(code int, err error) error {
3335
return exitCodeError{code: code, err: err}
3436
}
37+
38+
// exitWithErrorFromStatusCode maps HTTP status codes to CLI exit codes
39+
func exitWithErrorFromStatusCode(statusCode int, err error) error {
40+
if err == nil {
41+
err = errors.New("unknown error")
42+
}
43+
switch statusCode {
44+
case 400:
45+
// Bad request - invalid parameters
46+
return exitWithCode(ExitInvalidParameters, err)
47+
case 401:
48+
// Unauthorized - authentication error
49+
return exitWithCode(ExitAuthenticationError, err)
50+
case 403:
51+
// Forbidden - permission denied
52+
return exitWithCode(ExitPermissionDenied, err)
53+
case 404:
54+
// Not found - service/resource not found
55+
return exitWithCode(ExitServiceNotFound, err)
56+
case 408, 504:
57+
// Request timeout or gateway timeout
58+
return exitWithCode(ExitTimeout, err)
59+
default:
60+
// For other 4xx errors, use general error
61+
if statusCode >= 400 && statusCode < 500 {
62+
return exitWithCode(ExitGeneralError, err)
63+
}
64+
// For 5xx and other errors, use general error
65+
return exitWithCode(ExitGeneralError, err)
66+
}
67+
}

internal/tiger/cmd/integration_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ func executeIntegrationCommand(args ...string) (string, error) {
8383
// TestServiceLifecycleIntegration tests the complete authentication and service lifecycle:
8484
// login -> whoami -> create -> get -> update-password -> delete -> logout
8585
func TestServiceLifecycleIntegration(t *testing.T) {
86+
config.SetTestServiceName(t)
8687
// Check for required environment variables
8788
publicKey := os.Getenv("TIGER_PUBLIC_KEY_INTEGRATION")
8889
secretKey := os.Getenv("TIGER_SECRET_KEY_INTEGRATION")
@@ -399,8 +400,8 @@ func TestServiceLifecycleIntegration(t *testing.T) {
399400
}
400401

401402
// Check that error indicates service not found
402-
if !strings.Contains(err.Error(), "not found") && !strings.Contains(err.Error(), "404") {
403-
t.Errorf("Expected 'not found' error for deleted service, got: %v", err)
403+
if !strings.Contains(err.Error(), "no service with that id exists") {
404+
t.Errorf("Expected 'no service with that id exists' error for deleted service, got: %v", err)
404405
}
405406

406407
// Check that it returns the correct exit code (this should be required)
@@ -489,6 +490,7 @@ func TestServiceNotFound(t *testing.T) {
489490
publicKey := os.Getenv("TIGER_PUBLIC_KEY_INTEGRATION")
490491
secretKey := os.Getenv("TIGER_SECRET_KEY_INTEGRATION")
491492
projectID := os.Getenv("TIGER_PROJECT_ID_INTEGRATION")
493+
config.SetTestServiceName(t)
492494

493495
if publicKey == "" || secretKey == "" || projectID == "" {
494496
t.Skip("Skipping service not found test: TIGER_PUBLIC_KEY_INTEGRATION, TIGER_SECRET_KEY_INTEGRATION, and TIGER_PROJECT_ID_INTEGRATION must be set")
@@ -602,6 +604,7 @@ func TestDatabaseCommandsIntegration(t *testing.T) {
602604
secretKey := os.Getenv("TIGER_SECRET_KEY_INTEGRATION")
603605
projectID := os.Getenv("TIGER_PROJECT_ID_INTEGRATION")
604606
existingServiceID := os.Getenv("TIGER_EXISTING_SERVICE_ID_INTEGRATION") // Optional: use existing service
607+
config.SetTestServiceName(t)
605608

606609
if publicKey == "" || secretKey == "" || projectID == "" {
607610
t.Skip("Skipping integration test: TIGER_PUBLIC_KEY_INTEGRATION, TIGER_SECRET_KEY_INTEGRATION, and TIGER_PROJECT_ID_INTEGRATION must be set")
@@ -666,6 +669,7 @@ func TestAuthenticationErrorsIntegration(t *testing.T) {
666669
publicKey := os.Getenv("TIGER_PUBLIC_KEY_INTEGRATION")
667670
secretKey := os.Getenv("TIGER_SECRET_KEY_INTEGRATION")
668671
projectID := os.Getenv("TIGER_PROJECT_ID_INTEGRATION")
672+
config.SetTestServiceName(t)
669673

670674
if publicKey == "" || secretKey == "" || projectID == "" {
671675
t.Skip("Skipping authentication error integration test: TIGER_PUBLIC_KEY_INTEGRATION, TIGER_SECRET_KEY_INTEGRATION, and TIGER_PROJECT_ID_INTEGRATION must be set")

0 commit comments

Comments
 (0)