Skip to content

Commit c6b1e8f

Browse files
committed
Query update CLI option
1 parent 1c9ba05 commit c6b1e8f

4 files changed

Lines changed: 215 additions & 0 deletions

File tree

cmd/query/query.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ func NewQueryCmd() *cobra.Command {
1010
}
1111
cmd.AddCommand(newCreateCmd())
1212
cmd.AddCommand(newGetCmd())
13+
cmd.AddCommand(newUpdateCmd())
1314
return cmd
1415
}

cmd/query/testutil_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type mockClient struct {
1616
dune.DuneClient
1717
createQueryFn func(models.CreateQueryRequest) (*models.CreateQueryResponse, error)
1818
getQueryFn func(int) (*models.GetQueryResponse, error)
19+
updateQueryFn func(int, models.UpdateQueryRequest) (*models.UpdateQueryResponse, error)
1920
}
2021

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

30+
func (m *mockClient) UpdateQuery(queryID int, req models.UpdateQueryRequest) (*models.UpdateQueryResponse, error) {
31+
return m.updateQueryFn(queryID, req)
32+
}
33+
2934
// newTestRoot builds a root → query command tree with the mock injected.
3035
func newTestRoot(mock dune.DuneClient) (*cobra.Command, *bytes.Buffer) {
3136
root := &cobra.Command{

cmd/query/update.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package query
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
7+
"github.com/duneanalytics/cli/cmdutil"
8+
"github.com/duneanalytics/cli/output"
9+
"github.com/duneanalytics/duneapi-client-go/models"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
func newUpdateCmd() *cobra.Command {
14+
cmd := &cobra.Command{
15+
Use: "update <query-id>",
16+
Short: "Update an existing saved query",
17+
Args: cobra.ExactArgs(1),
18+
RunE: runUpdate,
19+
}
20+
21+
cmd.Flags().String("name", "", "query name")
22+
cmd.Flags().String("sql", "", "query SQL")
23+
cmd.Flags().String("description", "", "query description")
24+
cmd.Flags().Bool("private", false, "make the query private")
25+
cmd.Flags().StringSlice("tags", nil, "query tags (comma-separated)")
26+
output.AddFormatFlag(cmd, "text")
27+
28+
return cmd
29+
}
30+
31+
func runUpdate(cmd *cobra.Command, args []string) error {
32+
queryID, err := strconv.Atoi(args[0])
33+
if err != nil {
34+
return fmt.Errorf("invalid query ID %q: must be an integer", args[0])
35+
}
36+
37+
var req models.UpdateQueryRequest
38+
changed := false
39+
40+
if cmd.Flags().Changed("name") {
41+
v, _ := cmd.Flags().GetString("name")
42+
req.Name = &v
43+
changed = true
44+
}
45+
if cmd.Flags().Changed("sql") {
46+
v, _ := cmd.Flags().GetString("sql")
47+
req.QuerySQL = &v
48+
changed = true
49+
}
50+
if cmd.Flags().Changed("description") {
51+
v, _ := cmd.Flags().GetString("description")
52+
req.Description = &v
53+
changed = true
54+
}
55+
if cmd.Flags().Changed("private") {
56+
v, _ := cmd.Flags().GetBool("private")
57+
req.IsPrivate = &v
58+
changed = true
59+
}
60+
if cmd.Flags().Changed("tags") {
61+
v, _ := cmd.Flags().GetStringSlice("tags")
62+
req.Tags = v
63+
changed = true
64+
}
65+
66+
if !changed {
67+
return fmt.Errorf("at least one flag must be provided (--name, --sql, --description, --private, or --tags)")
68+
}
69+
70+
client := cmdutil.ClientFromCmd(cmd)
71+
72+
resp, err := client.UpdateQuery(queryID, req)
73+
if err != nil {
74+
return err
75+
}
76+
77+
w := cmd.OutOrStdout()
78+
switch output.FormatFromCmd(cmd) {
79+
case output.FormatJSON:
80+
return output.PrintJSON(w, resp)
81+
default:
82+
fmt.Fprintf(w, "Updated query %d\n", resp.QueryID)
83+
return nil
84+
}
85+
}

cmd/query/update_test.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package query_test
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"testing"
7+
8+
"github.com/duneanalytics/duneapi-client-go/models"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestUpdateSingleFlag(t *testing.T) {
14+
mock := &mockClient{
15+
updateQueryFn: func(id int, req models.UpdateQueryRequest) (*models.UpdateQueryResponse, error) {
16+
assert.Equal(t, 4125432, id)
17+
require.NotNil(t, req.Name)
18+
assert.Equal(t, "New", *req.Name)
19+
assert.Nil(t, req.QuerySQL)
20+
assert.Nil(t, req.Description)
21+
assert.Nil(t, req.IsPrivate)
22+
assert.Nil(t, req.Tags)
23+
return &models.UpdateQueryResponse{QueryID: 4125432}, nil
24+
},
25+
}
26+
27+
root, buf := newTestRoot(mock)
28+
root.SetArgs([]string{"query", "update", "4125432", "--name", "New"})
29+
require.NoError(t, root.Execute())
30+
assert.Equal(t, "Updated query 4125432\n", buf.String())
31+
}
32+
33+
func TestUpdateMultipleFlags(t *testing.T) {
34+
mock := &mockClient{
35+
updateQueryFn: func(_ int, req models.UpdateQueryRequest) (*models.UpdateQueryResponse, error) {
36+
require.NotNil(t, req.Name)
37+
assert.Equal(t, "New", *req.Name)
38+
require.NotNil(t, req.QuerySQL)
39+
assert.Equal(t, "SELECT 2", *req.QuerySQL)
40+
assert.Equal(t, []string{"defi", "uniswap"}, req.Tags)
41+
assert.Nil(t, req.Description)
42+
assert.Nil(t, req.IsPrivate)
43+
return &models.UpdateQueryResponse{QueryID: 1}, nil
44+
},
45+
}
46+
47+
root, _ := newTestRoot(mock)
48+
root.SetArgs([]string{"query", "update", "1", "--name", "New", "--sql", "SELECT 2", "--tags", "defi,uniswap"})
49+
require.NoError(t, root.Execute())
50+
}
51+
52+
func TestUpdatePrivateFlag(t *testing.T) {
53+
mock := &mockClient{
54+
updateQueryFn: func(_ int, req models.UpdateQueryRequest) (*models.UpdateQueryResponse, error) {
55+
require.NotNil(t, req.IsPrivate)
56+
assert.True(t, *req.IsPrivate)
57+
return &models.UpdateQueryResponse{QueryID: 1}, nil
58+
},
59+
}
60+
61+
root, _ := newTestRoot(mock)
62+
root.SetArgs([]string{"query", "update", "1", "--private"})
63+
require.NoError(t, root.Execute())
64+
}
65+
66+
func TestUpdatePrivateFalse(t *testing.T) {
67+
mock := &mockClient{
68+
updateQueryFn: func(_ int, req models.UpdateQueryRequest) (*models.UpdateQueryResponse, error) {
69+
require.NotNil(t, req.IsPrivate)
70+
assert.False(t, *req.IsPrivate)
71+
return &models.UpdateQueryResponse{QueryID: 1}, nil
72+
},
73+
}
74+
75+
root, _ := newTestRoot(mock)
76+
root.SetArgs([]string{"query", "update", "1", "--private=false"})
77+
require.NoError(t, root.Execute())
78+
}
79+
80+
func TestUpdateNoFlags(t *testing.T) {
81+
root, _ := newTestRoot(&mockClient{})
82+
root.SetArgs([]string{"query", "update", "1"})
83+
err := root.Execute()
84+
require.Error(t, err)
85+
assert.Contains(t, err.Error(), "at least one flag")
86+
}
87+
88+
func TestUpdateNonIntegerID(t *testing.T) {
89+
root, _ := newTestRoot(&mockClient{})
90+
root.SetArgs([]string{"query", "update", "abc", "--name", "X"})
91+
err := root.Execute()
92+
require.Error(t, err)
93+
assert.Contains(t, err.Error(), "invalid query ID")
94+
}
95+
96+
func TestUpdateAPIError(t *testing.T) {
97+
mock := &mockClient{
98+
updateQueryFn: func(_ int, _ models.UpdateQueryRequest) (*models.UpdateQueryResponse, error) {
99+
return nil, errors.New("api: unauthorized")
100+
},
101+
}
102+
103+
root, _ := newTestRoot(mock)
104+
root.SetArgs([]string{"query", "update", "1", "--name", "X"})
105+
err := root.Execute()
106+
require.Error(t, err)
107+
assert.Contains(t, err.Error(), "api: unauthorized")
108+
}
109+
110+
func TestUpdateJSONOutput(t *testing.T) {
111+
mock := &mockClient{
112+
updateQueryFn: func(_ int, _ models.UpdateQueryRequest) (*models.UpdateQueryResponse, error) {
113+
return &models.UpdateQueryResponse{QueryID: 4125432}, nil
114+
},
115+
}
116+
117+
root, buf := newTestRoot(mock)
118+
root.SetArgs([]string{"query", "update", "4125432", "--name", "New", "-o", "json"})
119+
require.NoError(t, root.Execute())
120+
121+
var got map[string]int
122+
require.NoError(t, json.Unmarshal(buf.Bytes(), &got))
123+
assert.Equal(t, 4125432, got["query_id"])
124+
}

0 commit comments

Comments
 (0)