Skip to content

Commit 53e1356

Browse files
cevianclaude
andcommitted
Fix authentication error exit codes and add comprehensive integration testing
- Fix authentication required errors to return exit code 4 (ExitAuthenticationError) instead of default exit code 1 - Update all service commands (list, describe, create, update-password, delete) to use proper exit codes for auth failures - Update db commands (connection-string, connect) to use proper exit codes for auth failures - Maintain db test-connection exit code 3 per pg_isready conventions - Add comprehensive integration test TestAuthenticationErrorsIntegration that verifies all commands return correct exit codes with invalid API keys - Add unit tests for ExitAuthenticationError and ExitPermissionDenied exit code handling - Update TODO.md to mark authentication error exit codes task as complete 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent fa1fe3a commit 53e1356

File tree

6 files changed

+262
-72
lines changed

6 files changed

+262
-72
lines changed

TODO.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@ Blocked:
55
- need to add JWT/OATH2 support so that users don't handle API keys
66

77
Active List:
8-
- update exit codes for authentication and permission errors
9-
- test the --password-storage flag with none option and PGPASSWORD env var
10-
- add integration test for service 404s
118
- add release tooling
129
- goreleaser?
1310
- platforms:
@@ -16,6 +13,9 @@ Active List:
1613
- curl .sh script
1714

1815
Done:
16+
- ✅ update exit codes for authentication and permission errors
17+
- ✅ test the --password-storage flag with none option and PGPASSWORD env var
18+
- ✅ add integration test for service 404s
1919
- ✅ api key stuff right now is messy. we require setting the api key from the public key and private key as public_key:private_key. should be a single string instead.
2020
- ✅ change service operations to use --wait-timeout instead of --timeout
2121
- ✅ implement --wait-timeout flag to accept time.ParseDuration format (e.g., "30m", "1h30m", "90s")

internal/tiger/cmd/db.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ func getServiceDetails(cmd *cobra.Command, args []string) (api.Service, error) {
291291
// Get API key for authentication
292292
apiKey, err := getAPIKeyForDB()
293293
if err != nil {
294-
return api.Service{}, fmt.Errorf("authentication required: %w", err)
294+
return api.Service{}, exitWithCode(ExitAuthenticationError, fmt.Errorf("authentication required: %w", err))
295295
}
296296

297297
// Create API client
@@ -318,8 +318,10 @@ func getServiceDetails(cmd *cobra.Command, args []string) (api.Service, error) {
318318

319319
return *resp.JSON200, nil
320320

321-
case 401, 403:
322-
return api.Service{}, fmt.Errorf("authentication failed: invalid API key")
321+
case 401:
322+
return api.Service{}, exitWithCode(ExitAuthenticationError, fmt.Errorf("authentication failed: invalid API key"))
323+
case 403:
324+
return api.Service{}, exitWithCode(ExitPermissionDenied, fmt.Errorf("permission denied: insufficient access to service"))
323325
case 404:
324326
return api.Service{}, exitWithCode(ExitServiceNotFound, fmt.Errorf("service '%s' not found in project '%s'", serviceID, projectID))
325327
default:
@@ -363,7 +365,7 @@ func buildPsqlCommand(connectionString, psqlPath string, additionalFlags []strin
363365
args = append(args, additionalFlags...)
364366

365367
psqlCmd := exec.Command(psqlPath, args...)
366-
368+
367369
// Use cmd's input/output streams for testability while maintaining CLI behavior
368370
psqlCmd.Stdin = cmd.InOrStdin()
369371
psqlCmd.Stdout = cmd.OutOrStdout()

internal/tiger/cmd/errors.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ package cmd
22

33
// Exit codes as defined in the CLI specification
44
const (
5-
ExitSuccess = 0 // Success
6-
ExitGeneralError = 1 // General error
7-
ExitTimeout = 2 // Operation timeout (wait-timeout exceeded) or connection timeout
8-
ExitInvalidParameters = 3 // Invalid parameters
5+
ExitSuccess = 0 // Success
6+
ExitGeneralError = 1 // General error
7+
ExitTimeout = 2 // Operation timeout (wait-timeout exceeded) or connection timeout
8+
ExitInvalidParameters = 3 // Invalid parameters
99
ExitAuthenticationError = 4 // Authentication error
10-
ExitPermissionDenied = 5 // Permission denied
11-
ExitServiceNotFound = 6 // Service not found
10+
ExitPermissionDenied = 5 // Permission denied
11+
ExitServiceNotFound = 6 // Service not found
1212
)
1313

1414
// exitCodeError creates an error that will cause the program to exit with the specified code

internal/tiger/cmd/errors_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,43 @@ func TestExitCodeError_NilError(t *testing.T) {
3838
t.Error("exitWithCode should return exitCodeError")
3939
}
4040
}
41+
42+
func TestExitAuthenticationError(t *testing.T) {
43+
originalErr := fmt.Errorf("authentication failed: invalid API key")
44+
exitErr := exitWithCode(ExitAuthenticationError, originalErr)
45+
46+
if exitErr.Error() != "authentication failed: invalid API key" {
47+
t.Errorf("Expected error message 'authentication failed: invalid API key', got '%s'", exitErr.Error())
48+
}
49+
50+
if exitCodeErr, ok := exitErr.(interface{ ExitCode() int }); ok {
51+
if exitCodeErr.ExitCode() != ExitAuthenticationError {
52+
t.Errorf("Expected exit code %d (ExitAuthenticationError), got %d", ExitAuthenticationError, exitCodeErr.ExitCode())
53+
}
54+
if exitCodeErr.ExitCode() != 4 {
55+
t.Errorf("Expected exit code 4 for authentication error, got %d", exitCodeErr.ExitCode())
56+
}
57+
} else {
58+
t.Error("exitWithCode should return exitCodeError with ExitCode method")
59+
}
60+
}
61+
62+
func TestExitPermissionDenied(t *testing.T) {
63+
originalErr := fmt.Errorf("permission denied: insufficient access to service")
64+
exitErr := exitWithCode(ExitPermissionDenied, originalErr)
65+
66+
if exitErr.Error() != "permission denied: insufficient access to service" {
67+
t.Errorf("Expected error message 'permission denied: insufficient access to service', got '%s'", exitErr.Error())
68+
}
69+
70+
if exitCodeErr, ok := exitErr.(interface{ ExitCode() int }); ok {
71+
if exitCodeErr.ExitCode() != ExitPermissionDenied {
72+
t.Errorf("Expected exit code %d (ExitPermissionDenied), got %d", ExitPermissionDenied, exitCodeErr.ExitCode())
73+
}
74+
if exitCodeErr.ExitCode() != 5 {
75+
t.Errorf("Expected exit code 5 for permission denied, got %d", exitCodeErr.ExitCode())
76+
}
77+
} else {
78+
t.Error("exitWithCode should return exitCodeError with ExitCode method")
79+
}
80+
}

0 commit comments

Comments
 (0)