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
1 change: 1 addition & 0 deletions cmd/query/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ func NewQueryCmd() *cobra.Command {
}
cmd.AddCommand(newCreateCmd())
cmd.AddCommand(newGetCmd())
cmd.AddCommand(newUpdateCmd())
return cmd
}
5 changes: 5 additions & 0 deletions cmd/query/testutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type mockClient struct {
dune.DuneClient
createQueryFn func(models.CreateQueryRequest) (*models.CreateQueryResponse, error)
getQueryFn func(int) (*models.GetQueryResponse, error)
updateQueryFn func(int, models.UpdateQueryRequest) (*models.UpdateQueryResponse, error)
}

func (m *mockClient) CreateQuery(req models.CreateQueryRequest) (*models.CreateQueryResponse, error) {
Expand All @@ -26,6 +27,10 @@ func (m *mockClient) GetQuery(queryID int) (*models.GetQueryResponse, error) {
return m.getQueryFn(queryID)
}

func (m *mockClient) UpdateQuery(queryID int, req models.UpdateQueryRequest) (*models.UpdateQueryResponse, error) {
return m.updateQueryFn(queryID, req)
}

// newTestRoot builds a root → query command tree with the mock injected.
func newTestRoot(mock dune.DuneClient) (*cobra.Command, *bytes.Buffer) {
root := &cobra.Command{
Expand Down
85 changes: 85 additions & 0 deletions cmd/query/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package query

import (
"fmt"
"strconv"

"github.com/duneanalytics/cli/cmdutil"
"github.com/duneanalytics/cli/output"
"github.com/duneanalytics/duneapi-client-go/models"
"github.com/spf13/cobra"
)

func newUpdateCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "update <query-id>",
Short: "Update an existing saved query",
Args: cobra.ExactArgs(1),
RunE: runUpdate,
}

cmd.Flags().String("name", "", "query name")
cmd.Flags().String("sql", "", "query SQL")
cmd.Flags().String("description", "", "query description")
cmd.Flags().Bool("private", false, "make the query private")
cmd.Flags().StringSlice("tags", nil, "query tags (comma-separated)")
output.AddFormatFlag(cmd, "text")

return cmd
}

func runUpdate(cmd *cobra.Command, args []string) error {
queryID, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid query ID %q: must be an integer", args[0])
}

var req models.UpdateQueryRequest
changed := false

if cmd.Flags().Changed("name") {
v, _ := cmd.Flags().GetString("name")
req.Name = &v
changed = true
}
if cmd.Flags().Changed("sql") {
v, _ := cmd.Flags().GetString("sql")
req.QuerySQL = &v
changed = true
}
if cmd.Flags().Changed("description") {
v, _ := cmd.Flags().GetString("description")
req.Description = &v
changed = true
}
if cmd.Flags().Changed("private") {
v, _ := cmd.Flags().GetBool("private")
req.IsPrivate = &v
changed = true
}
if cmd.Flags().Changed("tags") {
v, _ := cmd.Flags().GetStringSlice("tags")
req.Tags = v
changed = true
}

if !changed {
return fmt.Errorf("at least one flag must be provided (--name, --sql, --description, --private, or --tags)")
}

client := cmdutil.ClientFromCmd(cmd)

resp, err := client.UpdateQuery(queryID, req)
if err != nil {
return err
}

w := cmd.OutOrStdout()
switch output.FormatFromCmd(cmd) {
case output.FormatJSON:
return output.PrintJSON(w, resp)
default:
fmt.Fprintf(w, "Updated query %d\n", resp.QueryID)
return nil
}
}
124 changes: 124 additions & 0 deletions cmd/query/update_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package query_test

import (
"encoding/json"
"errors"
"testing"

"github.com/duneanalytics/duneapi-client-go/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestUpdateSingleFlag(t *testing.T) {
mock := &mockClient{
updateQueryFn: func(id int, req models.UpdateQueryRequest) (*models.UpdateQueryResponse, error) {
assert.Equal(t, 4125432, id)
require.NotNil(t, req.Name)
assert.Equal(t, "New", *req.Name)
assert.Nil(t, req.QuerySQL)
assert.Nil(t, req.Description)
assert.Nil(t, req.IsPrivate)
assert.Nil(t, req.Tags)
return &models.UpdateQueryResponse{QueryID: 4125432}, nil
},
}

root, buf := newTestRoot(mock)
root.SetArgs([]string{"query", "update", "4125432", "--name", "New"})
require.NoError(t, root.Execute())
assert.Equal(t, "Updated query 4125432\n", buf.String())
}

func TestUpdateMultipleFlags(t *testing.T) {
mock := &mockClient{
updateQueryFn: func(_ int, req models.UpdateQueryRequest) (*models.UpdateQueryResponse, error) {
require.NotNil(t, req.Name)
assert.Equal(t, "New", *req.Name)
require.NotNil(t, req.QuerySQL)
assert.Equal(t, "SELECT 2", *req.QuerySQL)
assert.Equal(t, []string{"defi", "uniswap"}, req.Tags)
assert.Nil(t, req.Description)
assert.Nil(t, req.IsPrivate)
return &models.UpdateQueryResponse{QueryID: 1}, nil
},
}

root, _ := newTestRoot(mock)
root.SetArgs([]string{"query", "update", "1", "--name", "New", "--sql", "SELECT 2", "--tags", "defi,uniswap"})
require.NoError(t, root.Execute())
}

func TestUpdatePrivateFlag(t *testing.T) {
mock := &mockClient{
updateQueryFn: func(_ int, req models.UpdateQueryRequest) (*models.UpdateQueryResponse, error) {
require.NotNil(t, req.IsPrivate)
assert.True(t, *req.IsPrivate)
return &models.UpdateQueryResponse{QueryID: 1}, nil
},
}

root, _ := newTestRoot(mock)
root.SetArgs([]string{"query", "update", "1", "--private"})
require.NoError(t, root.Execute())
}

func TestUpdatePrivateFalse(t *testing.T) {
mock := &mockClient{
updateQueryFn: func(_ int, req models.UpdateQueryRequest) (*models.UpdateQueryResponse, error) {
require.NotNil(t, req.IsPrivate)
assert.False(t, *req.IsPrivate)
return &models.UpdateQueryResponse{QueryID: 1}, nil
},
}

root, _ := newTestRoot(mock)
root.SetArgs([]string{"query", "update", "1", "--private=false"})
require.NoError(t, root.Execute())
}

func TestUpdateNoFlags(t *testing.T) {
root, _ := newTestRoot(&mockClient{})
root.SetArgs([]string{"query", "update", "1"})
err := root.Execute()
require.Error(t, err)
assert.Contains(t, err.Error(), "at least one flag")
}

func TestUpdateNonIntegerID(t *testing.T) {
root, _ := newTestRoot(&mockClient{})
root.SetArgs([]string{"query", "update", "abc", "--name", "X"})
err := root.Execute()
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid query ID")
}

func TestUpdateAPIError(t *testing.T) {
mock := &mockClient{
updateQueryFn: func(_ int, _ models.UpdateQueryRequest) (*models.UpdateQueryResponse, error) {
return nil, errors.New("api: unauthorized")
},
}

root, _ := newTestRoot(mock)
root.SetArgs([]string{"query", "update", "1", "--name", "X"})
err := root.Execute()
require.Error(t, err)
assert.Contains(t, err.Error(), "api: unauthorized")
}

func TestUpdateJSONOutput(t *testing.T) {
mock := &mockClient{
updateQueryFn: func(_ int, _ models.UpdateQueryRequest) (*models.UpdateQueryResponse, error) {
return &models.UpdateQueryResponse{QueryID: 4125432}, nil
},
}

root, buf := newTestRoot(mock)
root.SetArgs([]string{"query", "update", "4125432", "--name", "New", "-o", "json"})
require.NoError(t, root.Execute())

var got map[string]int
require.NoError(t, json.Unmarshal(buf.Bytes(), &got))
assert.Equal(t, 4125432, got["query_id"])
}