diff --git a/README.md b/README.md index d30de63..0acbda4 100644 --- a/README.md +++ b/README.md @@ -465,8 +465,6 @@ hookdeck connection enable # Enable a connection hookdeck connection disable # Disable a connection hookdeck connection pause # Pause a connection hookdeck connection unpause # Unpause a connection -hookdeck connection archive # Archive a connection -hookdeck connection unarchive # Unarchive a connection ``` ### Manage active project @@ -724,9 +722,8 @@ $ hookdeck connection list --destination dest_xyz789 # Filter by name pattern $ hookdeck connection list --name "production-*" -# Include disabled or paused connections +# Include disabled connections $ hookdeck connection list --disabled -$ hookdeck connection list --paused # Output as JSON $ hookdeck connection list --output json @@ -763,18 +760,11 @@ $ hookdeck connection pause conn_123abc # Resume a paused connection $ hookdeck connection unpause conn_123abc - -# Archive a connection (hide from main lists) -$ hookdeck connection archive conn_123abc - -# Restore an archived connection -$ hookdeck connection unarchive conn_123abc ``` **State differences:** - **Disabled**: Connection stops receiving events entirely - **Paused**: Connection queues events but doesn't forward them (useful during maintenance) -- **Archived**: Connection is hidden from main lists but can be restored #### Delete a connection diff --git a/REFERENCE.md b/REFERENCE.md index 8791182..5dad4ae 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -51,10 +51,16 @@ All commands support these global options: --help, -h Show help information ``` -### 🚧 Planned Global Options +### 🔄 Partially Implemented Options +```bash +--output json Output in JSON format (available on: connection create/list/get/upsert) + Default: human-readable format +``` + +### � Planned Global Options ```bash --project string Project ID to use (overrides profile) ---format string Output format: table, json, yaml (default "table") +--output string Additional output formats: table, yaml (currently only json supported) ``` ## Authentication @@ -293,7 +299,7 @@ The Hookdeck CLI provides comprehensive connection management capabilities. The | Project Management | 🔄 **Partial** | `project list`, `project use` | | Local Development | ✅ **Current** | `listen` | | CI/CD | ✅ **Current** | `ci` | -| Connection Management | ✅ **Current** | `connection create`, `connection list`, `connection get`, `connection upsert`, `connection delete`, `connection enable`, `connection disable`, `connection pause`, `connection unpause`, `connection archive`, `connection unarchive` | +| Connection Management | ✅ **Current** | `connection create`, `connection list`, `connection get`, `connection upsert`, `connection delete`, `connection enable`, `connection disable`, `connection pause`, `connection unpause` | | Shell Completion | ✅ **Current** | `completion` (bash, zsh) | | Source Management | 🚧 **Planned** | *(Not implemented)* | | Destination Management | 🚧 **Planned** | *(Not implemented)* | @@ -450,7 +456,7 @@ hookdeck project delete proj_123 --force # Required positional argument for source ID --force # Force delete without confirmation (boolean flag) -# Source enable/disable/archive/unarchive command parameters +# Source enable/disable command parameters # Required positional argument for source ID ``` @@ -662,12 +668,6 @@ hookdeck source enable # Disable source hookdeck source disable - -# Archive source -hookdeck source archive - -# Unarchive source -hookdeck source unarchive ``` ## Destinations @@ -729,7 +729,7 @@ hookdeck source unarchive # Required positional argument for destination ID --force # Force delete without confirmation (boolean flag) -# Destination enable/disable/archive/unarchive command parameters +# Destination enable/disable command parameters # Required positional argument for destination ID ``` @@ -863,12 +863,6 @@ hookdeck destination enable # Disable destination hookdeck destination disable - -# Archive destination -hookdeck destination archive - -# Unarchive destination -hookdeck destination unarchive ``` ## Connections @@ -883,7 +877,6 @@ hookdeck destination unarchive - `connection delete` - Delete connections with confirmation - `connection enable/disable` - Control connection state - `connection pause/unpause` - Pause/resume event processing -- `connection archive/unarchive` - Archive inactive connections **Implementation Status:** - ✅ Full CRUD operations @@ -902,36 +895,34 @@ hookdeck destination unarchive # List all connections hookdeck connection list -# Filter by source -hookdeck connection list --source src_abc123 +# Filter by source ID +hookdeck connection list --source-id src_abc123 -# Filter by destination -hookdeck connection list --destination dest_xyz789 +# Filter by destination ID +hookdeck connection list --destination-id dest_xyz789 -# Filter by name pattern -hookdeck connection list --name "production-*" +# Filter by connection name +hookdeck connection list --name "production-connection" # Include disabled connections hookdeck connection list --disabled -# Include paused connections -hookdeck connection list --paused +# Combine filters +hookdeck connection list --source-id src_abc123 --disabled -# Include archived connections -hookdeck connection list --archived +# Limit results +hookdeck connection list --limit 50 -# Combine filters -hookdeck connection list --source src_abc123 --disabled +# Output as JSON +hookdeck connection list --output json ``` **Available Flags:** -- `--source ` - Filter by source ID or name -- `--destination ` - Filter by destination ID or name -- `--name ` - Filter by connection name -- `--full-name ` - Filter by full connection name (source > connection > destination) -- `--disabled` - Show only disabled connections -- `--paused` - Show only paused connections -- `--archived` - Show only archived connections +- `--name ` - Filter by connection name +- `--source-id ` - Filter by source ID +- `--destination-id ` - Filter by destination ID +- `--disabled` - Include disabled connections +- `--limit ` - Limit number of results (default: 100) - `--output json` - Output in JSON format ### Get Connection @@ -1236,16 +1227,11 @@ hookdeck connection enable conn_abc123 # Pause/Unpause (queue events without forwarding) hookdeck connection pause conn_abc123 hookdeck connection unpause conn_abc123 - -# Archive/Unarchive (for inactive connections) -hookdeck connection archive conn_abc123 -hookdeck connection unarchive conn_abc123 ``` **State Differences:** - **Disabled**: Connection stops receiving events entirely - **Paused**: Connection queues events but doesn't forward them -- **Archived**: Connection is hidden from main lists but can be restored ### Implementation Notes @@ -1254,7 +1240,7 @@ hookdeck connection unarchive conn_abc123 - Inline resource creation with authentication - All 5 rule types (retry, filter, transform, delay, deduplicate) - Rate limiting configuration -- Lifecycle management (enable, disable, pause, unpause, archive, unarchive) +- Lifecycle management (enable, disable, pause, unpause) - Idempotent upsert with dry-run support - 21 acceptance tests, all passing diff --git a/pkg/cmd/connection.go b/pkg/cmd/connection.go index 6917093..7252589 100644 --- a/pkg/cmd/connection.go +++ b/pkg/cmd/connection.go @@ -37,8 +37,6 @@ https://github.com/hookdeck/hookdeck-cli/issues`, cc.cmd.AddCommand(newConnectionDisableCmd().cmd) cc.cmd.AddCommand(newConnectionPauseCmd().cmd) cc.cmd.AddCommand(newConnectionUnpauseCmd().cmd) - cc.cmd.AddCommand(newConnectionArchiveCmd().cmd) - cc.cmd.AddCommand(newConnectionUnarchiveCmd().cmd) return cc } diff --git a/pkg/cmd/connection_archive.go b/pkg/cmd/connection_archive.go deleted file mode 100644 index fb3817a..0000000 --- a/pkg/cmd/connection_archive.go +++ /dev/null @@ -1,48 +0,0 @@ -package cmd - -import ( - "context" - "fmt" - - "github.com/spf13/cobra" - - "github.com/hookdeck/hookdeck-cli/pkg/validators" -) - -type connectionArchiveCmd struct { - cmd *cobra.Command -} - -func newConnectionArchiveCmd() *connectionArchiveCmd { - cc := &connectionArchiveCmd{} - - cc.cmd = &cobra.Command{ - Use: "archive ", - Args: validators.ExactArgs(1), - Short: "Archive a connection", - Long: `Archive a connection. - -The connection will be archived and hidden from active lists.`, - RunE: cc.runConnectionArchiveCmd, - } - - return cc -} - -func (cc *connectionArchiveCmd) runConnectionArchiveCmd(cmd *cobra.Command, args []string) error { - client := Config.GetAPIClient() - ctx := context.Background() - - conn, err := client.ArchiveConnection(ctx, args[0]) - if err != nil { - return fmt.Errorf("failed to archive connection: %w", err) - } - - name := "unnamed" - if conn.Name != nil { - name = *conn.Name - } - - fmt.Printf("✓ Connection archived: %s (%s)\n", name, conn.ID) - return nil -} diff --git a/pkg/cmd/connection_list.go b/pkg/cmd/connection_list.go index 92a4e01..2875ca2 100644 --- a/pkg/cmd/connection_list.go +++ b/pkg/cmd/connection_list.go @@ -20,7 +20,6 @@ type connectionListCmd struct { sourceID string destinationID string disabled bool - paused bool limit int output string } @@ -50,9 +49,6 @@ Examples: # Include disabled connections hookdeck connection list --disabled - # Include paused connections - hookdeck connection list --paused - # Limit results hookdeck connection list --limit 10`, RunE: cc.runConnectionListCmd, @@ -62,7 +58,6 @@ Examples: cc.cmd.Flags().StringVar(&cc.sourceID, "source-id", "", "Filter by source ID") cc.cmd.Flags().StringVar(&cc.destinationID, "destination-id", "", "Filter by destination ID") cc.cmd.Flags().BoolVar(&cc.disabled, "disabled", false, "Include disabled connections") - cc.cmd.Flags().BoolVar(&cc.paused, "paused", false, "Include paused connections") cc.cmd.Flags().IntVar(&cc.limit, "limit", 100, "Limit number of results") cc.cmd.Flags().StringVar(&cc.output, "output", "", "Output format (json)") @@ -91,14 +86,24 @@ func (cc *connectionListCmd) runConnectionListCmd(cmd *cobra.Command, args []str params["destination_id"] = cc.destinationID } - if !cc.disabled { + // API behavior (tested in test-scripts/test-disabled-behavior.sh): + // - NO parameter: Returns ALL connections (both active and disabled) + // - disabled=false: Returns ONLY active connections (excludes disabled) + // - disabled=true: Returns ALL connections (both active and disabled) + // + // CLI behavior (from test expectations): + // - --disabled flag present: Include ALL connections (both active and disabled) + // - --disabled flag absent: Include only active connections + // + // Therefore: + // - When --disabled flag is PRESENT: Send disabled=true (to get all) + // - When --disabled flag is ABSENT: Send disabled=false (to exclude disabled) + if cc.disabled { + params["disabled"] = "true" + } else { params["disabled"] = "false" } - if !cc.paused { - params["paused"] = "false" - } - params["limit"] = strconv.Itoa(cc.limit) // List connections diff --git a/pkg/cmd/connection_unarchive.go b/pkg/cmd/connection_unarchive.go deleted file mode 100644 index 15845b8..0000000 --- a/pkg/cmd/connection_unarchive.go +++ /dev/null @@ -1,48 +0,0 @@ -package cmd - -import ( - "context" - "fmt" - - "github.com/spf13/cobra" - - "github.com/hookdeck/hookdeck-cli/pkg/validators" -) - -type connectionUnarchiveCmd struct { - cmd *cobra.Command -} - -func newConnectionUnarchiveCmd() *connectionUnarchiveCmd { - cc := &connectionUnarchiveCmd{} - - cc.cmd = &cobra.Command{ - Use: "unarchive ", - Args: validators.ExactArgs(1), - Short: "Restore an archived connection", - Long: `Restore an archived connection. - -The connection will be unarchived and visible in active lists.`, - RunE: cc.runConnectionUnarchiveCmd, - } - - return cc -} - -func (cc *connectionUnarchiveCmd) runConnectionUnarchiveCmd(cmd *cobra.Command, args []string) error { - client := Config.GetAPIClient() - ctx := context.Background() - - conn, err := client.UnarchiveConnection(ctx, args[0]) - if err != nil { - return fmt.Errorf("failed to unarchive connection: %w", err) - } - - name := "unnamed" - if conn.Name != nil { - name = *conn.Name - } - - fmt.Printf("✓ Connection unarchived: %s (%s)\n", name, conn.ID) - return nil -} diff --git a/pkg/hookdeck/connections.go b/pkg/hookdeck/connections.go index 2995f9c..37ce17f 100644 --- a/pkg/hookdeck/connections.go +++ b/pkg/hookdeck/connections.go @@ -222,38 +222,6 @@ func (c *Client) UnpauseConnection(ctx context.Context, id string) (*Connection, return &connection, nil } -// ArchiveConnection archives a connection -func (c *Client) ArchiveConnection(ctx context.Context, id string) (*Connection, error) { - resp, err := c.Put(ctx, fmt.Sprintf("/2025-07-01/connections/%s/archive", id), []byte("{}"), nil) - if err != nil { - return nil, err - } - - var connection Connection - _, err = postprocessJsonResponse(resp, &connection) - if err != nil { - return nil, fmt.Errorf("failed to parse connection response: %w", err) - } - - return &connection, nil -} - -// UnarchiveConnection unarchives a connection -func (c *Client) UnarchiveConnection(ctx context.Context, id string) (*Connection, error) { - resp, err := c.Put(ctx, fmt.Sprintf("/2025-07-01/connections/%s/unarchive", id), []byte("{}"), nil) - if err != nil { - return nil, err - } - - var connection Connection - _, err = postprocessJsonResponse(resp, &connection) - if err != nil { - return nil, fmt.Errorf("failed to parse connection response: %w", err) - } - - return &connection, nil -} - // CountConnections counts connections matching the given filters func (c *Client) CountConnections(ctx context.Context, params map[string]string) (*ConnectionCountResponse, error) { queryParams := url.Values{} diff --git a/pkg/hookdeck/connections_test.go b/pkg/hookdeck/connections_test.go index 9ec9cbd..b7319bf 100644 --- a/pkg/hookdeck/connections_test.go +++ b/pkg/hookdeck/connections_test.go @@ -755,162 +755,6 @@ func TestUnpauseConnection(t *testing.T) { } } -func TestArchiveConnection(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - connectionID string - mockResponse Connection - mockStatusCode int - wantErr bool - errContains string - }{ - { - name: "successful archive", - connectionID: "conn_123", - mockResponse: Connection{ - ID: "conn_123", - Name: stringPtr("test-connection"), - TeamID: "team_123", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - }, - mockStatusCode: http.StatusOK, - wantErr: false, - }, - { - name: "connection not found", - connectionID: "conn_nonexistent", - mockStatusCode: http.StatusNotFound, - wantErr: true, - errContains: "404", - }, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - client, server := newTestClient(func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPut { - t.Errorf("expected PUT request, got %s", r.Method) - } - expectedPath := "/2025-07-01/connections/" + tt.connectionID + "/archive" - if r.URL.Path != expectedPath { - t.Errorf("expected path %s, got %s", expectedPath, r.URL.Path) - } - - w.WriteHeader(tt.mockStatusCode) - if tt.mockStatusCode == http.StatusOK { - json.NewEncoder(w).Encode(tt.mockResponse) - } else { - json.NewEncoder(w).Encode(ErrorResponse{ - Message: "test error", - }) - } - }) - defer server.Close() - - result, err := client.ArchiveConnection(context.Background(), tt.connectionID) - - if tt.wantErr { - if err == nil { - t.Fatal("expected error, got nil") - } - return - } - - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - if result.ID != tt.mockResponse.ID { - t.Errorf("expected ID %s, got %s", tt.mockResponse.ID, result.ID) - } - }) - } -} - -func TestUnarchiveConnection(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - connectionID string - mockResponse Connection - mockStatusCode int - wantErr bool - errContains string - }{ - { - name: "successful unarchive", - connectionID: "conn_123", - mockResponse: Connection{ - ID: "conn_123", - Name: stringPtr("test-connection"), - TeamID: "team_123", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - }, - mockStatusCode: http.StatusOK, - wantErr: false, - }, - { - name: "connection not found", - connectionID: "conn_nonexistent", - mockStatusCode: http.StatusNotFound, - wantErr: true, - errContains: "404", - }, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - client, server := newTestClient(func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPut { - t.Errorf("expected PUT request, got %s", r.Method) - } - expectedPath := "/2025-07-01/connections/" + tt.connectionID + "/unarchive" - if r.URL.Path != expectedPath { - t.Errorf("expected path %s, got %s", expectedPath, r.URL.Path) - } - - w.WriteHeader(tt.mockStatusCode) - if tt.mockStatusCode == http.StatusOK { - json.NewEncoder(w).Encode(tt.mockResponse) - } else { - json.NewEncoder(w).Encode(ErrorResponse{ - Message: "test error", - }) - } - }) - defer server.Close() - - result, err := client.UnarchiveConnection(context.Background(), tt.connectionID) - - if tt.wantErr { - if err == nil { - t.Fatal("expected error, got nil") - } - return - } - - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - if result.ID != tt.mockResponse.ID { - t.Errorf("expected ID %s, got %s", tt.mockResponse.ID, result.ID) - } - }) - } -} - func TestCountConnections(t *testing.T) { t.Parallel() diff --git a/test/acceptance/connection_list_test.go b/test/acceptance/connection_list_test.go new file mode 100644 index 0000000..98f9276 --- /dev/null +++ b/test/acceptance/connection_list_test.go @@ -0,0 +1,263 @@ +package acceptance + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestConnectionListFilters tests the various filtering flags for connection list +func TestConnectionListFilters(t *testing.T) { + if testing.Short() { + t.Skip("Skipping acceptance test in short mode") + } + + t.Run("FilterDisabledConnections", func(t *testing.T) { + if testing.Short() { + t.Skip("Skipping acceptance test in short mode") + } + + cli := NewCLIRunner(t) + timestamp := generateTimestamp() + + connName := "test-disabled-filter-" + timestamp + sourceName := "test-disabled-src-" + timestamp + destName := "test-disabled-dst-" + timestamp + + // Create a connection + var conn Connection + err := cli.RunJSON(&conn, + "connection", "create", + "--name", connName, + "--source-name", sourceName, + "--source-type", "WEBHOOK", + "--destination-name", destName, + "--destination-type", "CLI", + "--destination-cli-path", "/webhooks", + ) + require.NoError(t, err, "Should create connection") + require.NotEmpty(t, conn.ID, "Connection should have an ID") + + // Cleanup + t.Cleanup(func() { + deleteConnection(t, cli, conn.ID) + }) + + // Verify connection is NOT in disabled list + stdout, stderr, err := cli.Run("connection", "list", "--disabled", "--output", "json") + require.NoError(t, err, "Should list disabled connections: stderr=%s", stderr) + + var disabledConns []Connection + err = json.Unmarshal([]byte(stdout), &disabledConns) + require.NoError(t, err, "Should parse JSON response") + + // Check that our connection IS in the disabled list (inclusive filtering) + // When --disabled is used, it shows ALL connections (both active and disabled) + found := false + for _, c := range disabledConns { + if c.ID == conn.ID { + found = true + break + } + } + assert.True(t, found, "Active connection should appear when --disabled flag is used (inclusive filtering)") + + // Disable the connection + _, stderr, err = cli.Run("connection", "disable", conn.ID) + require.NoError(t, err, "Should disable connection: stderr=%s", stderr) + + // Verify connection IS in disabled list + stdout, stderr, err = cli.Run("connection", "list", "--disabled", "--output", "json") + require.NoError(t, err, "Should list disabled connections: stderr=%s", stderr) + + err = json.Unmarshal([]byte(stdout), &disabledConns) + require.NoError(t, err, "Should parse JSON response") + + // Check that our connection IS now in the disabled list + found = false + for _, c := range disabledConns { + if c.ID == conn.ID { + found = true + break + } + } + assert.True(t, found, "Disabled connection should appear when filtering for disabled connections") + + // Verify connection is NOT in default list (without --disabled flag) + stdout, stderr, err = cli.Run("connection", "list", "--output", "json") + require.NoError(t, err, "Should list connections: stderr=%s", stderr) + + var activeConns []Connection + err = json.Unmarshal([]byte(stdout), &activeConns) + require.NoError(t, err, "Should parse JSON response") + + // Check that our disabled connection is NOT in the default list + found = false + for _, c := range activeConns { + if c.ID == conn.ID { + found = true + break + } + } + assert.False(t, found, "Disabled connection should not appear in default connection list") + + t.Logf("Successfully tested --disabled flag filtering: %s", conn.ID) + }) + + t.Run("FilterByName", func(t *testing.T) { + if testing.Short() { + t.Skip("Skipping acceptance test in short mode") + } + + cli := NewCLIRunner(t) + timestamp := generateTimestamp() + + connName := "test-name-filter-unique-" + timestamp + sourceName := "test-name-filter-src-" + timestamp + destName := "test-name-filter-dst-" + timestamp + + // Create a connection with a unique name + var conn Connection + err := cli.RunJSON(&conn, + "connection", "create", + "--name", connName, + "--source-name", sourceName, + "--source-type", "WEBHOOK", + "--destination-name", destName, + "--destination-type", "CLI", + "--destination-cli-path", "/webhooks", + ) + require.NoError(t, err, "Should create connection") + require.NotEmpty(t, conn.ID, "Connection should have an ID") + + // Cleanup + t.Cleanup(func() { + deleteConnection(t, cli, conn.ID) + }) + + // Filter by exact name + stdout, stderr, err := cli.Run("connection", "list", "--name", connName, "--output", "json") + require.NoError(t, err, "Should filter by name: stderr=%s", stderr) + + var filteredConns []Connection + err = json.Unmarshal([]byte(stdout), &filteredConns) + require.NoError(t, err, "Should parse JSON response") + + // Should find exactly our connection + found := false + for _, c := range filteredConns { + if c.ID == conn.ID { + found = true + assert.Equal(t, connName, c.Name, "Connection name should match") + break + } + } + assert.True(t, found, "Should find connection when filtering by exact name") + + t.Logf("Successfully tested --name flag filtering: %s", conn.ID) + }) + + t.Run("FilterBySourceID", func(t *testing.T) { + if testing.Short() { + t.Skip("Skipping acceptance test in short mode") + } + + cli := NewCLIRunner(t) + timestamp := generateTimestamp() + + connName := "test-source-filter-" + timestamp + sourceName := "test-source-filter-src-" + timestamp + destName := "test-source-filter-dst-" + timestamp + + // Create a connection + var conn Connection + err := cli.RunJSON(&conn, + "connection", "create", + "--name", connName, + "--source-name", sourceName, + "--source-type", "WEBHOOK", + "--destination-name", destName, + "--destination-type", "CLI", + "--destination-cli-path", "/webhooks", + ) + require.NoError(t, err, "Should create connection") + require.NotEmpty(t, conn.ID, "Connection should have an ID") + + // Get source ID from the created connection + var getResp map[string]interface{} + err = cli.RunJSON(&getResp, "connection", "get", conn.ID) + require.NoError(t, err, "Should get connection details") + + source, ok := getResp["source"].(map[string]interface{}) + require.True(t, ok, "Expected source object") + sourceID, ok := source["id"].(string) + require.True(t, ok && sourceID != "", "Expected source ID") + + // Cleanup + t.Cleanup(func() { + deleteConnection(t, cli, conn.ID) + }) + + // Filter by source ID + stdout, stderr, err := cli.Run("connection", "list", "--source-id", sourceID, "--output", "json") + require.NoError(t, err, "Should filter by source ID: stderr=%s", stderr) + + var filteredConns []Connection + err = json.Unmarshal([]byte(stdout), &filteredConns) + require.NoError(t, err, "Should parse JSON response") + + // Should find our connection + found := false + for _, c := range filteredConns { + if c.ID == conn.ID { + found = true + break + } + } + assert.True(t, found, "Should find connection when filtering by source ID") + + t.Logf("Successfully tested --source-id flag filtering: source=%s, conn=%s", sourceID, conn.ID) + }) + + t.Run("FilterByLimit", func(t *testing.T) { + if testing.Short() { + t.Skip("Skipping acceptance test in short mode") + } + + cli := NewCLIRunner(t) + + // List with limit of 5 + stdout, stderr, err := cli.Run("connection", "list", "--limit", "5", "--output", "json") + require.NoError(t, err, "Should list with limit: stderr=%s", stderr) + + var conns []Connection + err = json.Unmarshal([]byte(stdout), &conns) + require.NoError(t, err, "Should parse JSON response") + + // Should have at most 5 connections + assert.LessOrEqual(t, len(conns), 5, "Should respect limit parameter") + + t.Logf("Successfully tested --limit flag: returned %d connections (max 5)", len(conns)) + }) + + t.Run("HumanReadableOutput", func(t *testing.T) { + if testing.Short() { + t.Skip("Skipping acceptance test in short mode") + } + + cli := NewCLIRunner(t) + + // List without --output json to get human-readable format + stdout := cli.RunExpectSuccess("connection", "list") + + // Should contain human-readable text + assert.True(t, + strings.Contains(stdout, "connection") || strings.Contains(stdout, "No connections found"), + "Should produce human-readable output") + + t.Logf("Successfully tested human-readable output format") + }) +}