Skip to content

Commit ebe67c5

Browse files
committed
feat: add --dry-run flag to toggle-on, toggle-off, and archive commands
The API already supports dryRun as a query parameter on PATCH /api/v2/flags. The auto-generated `flags update` command exposes it, but the hand-rolled toggle and archive commands passed nil for query params. This wires --dry-run through to MakeRequest on all three. Also adds a Query field to MockClient so tests can verify query params. Made-with: Cursor
1 parent 5b7bfe3 commit ebe67c5

6 files changed

Lines changed: 106 additions & 2 deletions

File tree

cmd/cliflags/flags.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const (
3333
CorsEnabledFlag = "cors-enabled"
3434
CorsOriginFlag = "cors-origin"
3535
DataFlag = "data"
36+
DryRunFlag = "dry-run"
3637
DevStreamURIFlag = "dev-stream-uri"
3738
EmailsFlag = "emails"
3839
EnvironmentFlag = "environment"
@@ -51,6 +52,7 @@ const (
5152
CorsEnabledFlagDescription = "Enable CORS headers for browser-based developer tools (default: false)"
5253
CorsOriginFlagDescription = "Allowed CORS origin. Use '*' for all origins (default: '*')"
5354
DevStreamURIDescription = "Streaming service endpoint that the dev server uses to obtain authoritative flag data. This may be a LaunchDarkly or Relay Proxy endpoint"
55+
DryRunFlagDescription = "Validate the change without persisting it. Returns a preview of the result."
5456
EnvironmentFlagDescription = "Default environment key"
5557
FieldsFlagDescription = "Comma-separated list of top-level fields to include in JSON output (e.g., --fields key,name,kind)"
5658
FlagFlagDescription = "Default feature flag key"

cmd/flags/archive.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,16 @@ func makeArchiveRequest(client resources.Client) func(*cobra.Command, []string)
3838
viper.GetString(cliflags.ProjectFlag),
3939
viper.GetString(cliflags.FlagFlag),
4040
)
41+
var query url.Values
42+
if dryRun, _ := cmd.Flags().GetBool(cliflags.DryRunFlag); dryRun {
43+
query = url.Values{"dryRun": []string{"true"}}
44+
}
4145
res, err := client.MakeRequest(
4246
viper.GetString(cliflags.AccessTokenFlag),
4347
"PATCH",
4448
path,
4549
"application/json",
46-
nil,
50+
query,
4751
[]byte(`[{"op": "replace", "path": "/archived", "value": true}]`),
4852
false,
4953
)
@@ -75,4 +79,6 @@ func initArchiveFlags(cmd *cobra.Command) {
7579
_ = cmd.MarkFlagRequired(cliflags.ProjectFlag)
7680
_ = cmd.Flags().SetAnnotation(cliflags.ProjectFlag, "required", []string{"true"})
7781
_ = viper.BindPFlag(cliflags.ProjectFlag, cmd.Flags().Lookup(cliflags.ProjectFlag))
82+
83+
cmd.Flags().Bool(cliflags.DryRunFlag, false, cliflags.DryRunFlagDescription)
7884
}

cmd/flags/archive_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,49 @@ func TestArchive(t *testing.T) {
155155
assert.Contains(t, string(output), "test-flag")
156156
})
157157

158+
t.Run("passes dryRun query param when --dry-run is set", func(t *testing.T) {
159+
args := []string{
160+
"flags", "archive",
161+
"--access-token", "abcd1234",
162+
"--flag", "test-flag",
163+
"--project", "test-proj",
164+
"--output", "json",
165+
"--dry-run",
166+
}
167+
_, err := cmd.CallCmd(
168+
t,
169+
cmd.APIClients{
170+
ResourcesClient: mockClient,
171+
},
172+
analytics.NoopClientFn{}.Tracker(),
173+
args,
174+
)
175+
176+
require.NoError(t, err)
177+
assert.Equal(t, "true", mockClient.Query.Get("dryRun"))
178+
})
179+
180+
t.Run("does not pass dryRun query param by default", func(t *testing.T) {
181+
args := []string{
182+
"flags", "archive",
183+
"--access-token", "abcd1234",
184+
"--flag", "test-flag",
185+
"--project", "test-proj",
186+
"--output", "json",
187+
}
188+
_, err := cmd.CallCmd(
189+
t,
190+
cmd.APIClients{
191+
ResourcesClient: mockClient,
192+
},
193+
analytics.NoopClientFn{}.Tracker(),
194+
args,
195+
)
196+
197+
require.NoError(t, err)
198+
assert.Empty(t, mockClient.Query.Get("dryRun"))
199+
})
200+
158201
t.Run("returns error with missing flags", func(t *testing.T) {
159202
args := []string{
160203
"flags", "archive",

cmd/flags/toggle.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,16 @@ func runE(client resources.Client) func(*cobra.Command, []string) error {
6060
viper.GetString(cliflags.ProjectFlag),
6161
viper.GetString(cliflags.FlagFlag),
6262
)
63+
var query url.Values
64+
if dryRun, _ := cmd.Flags().GetBool(cliflags.DryRunFlag); dryRun {
65+
query = url.Values{"dryRun": []string{"true"}}
66+
}
6367
res, err := client.MakeRequest(
6468
viper.GetString(cliflags.AccessTokenFlag),
6569
"PATCH",
6670
path,
6771
"application/json",
68-
nil,
72+
query,
6973
[]byte(buildPatch(viper.GetString("environment"), toggleOn)),
7074
false,
7175
)
@@ -102,6 +106,8 @@ func initFlags(cmd *cobra.Command) {
102106
_ = cmd.MarkFlagRequired(cliflags.ProjectFlag)
103107
_ = cmd.Flags().SetAnnotation(cliflags.ProjectFlag, "required", []string{"true"})
104108
_ = viper.BindPFlag(cliflags.ProjectFlag, cmd.Flags().Lookup(cliflags.ProjectFlag))
109+
110+
cmd.Flags().Bool(cliflags.DryRunFlag, false, cliflags.DryRunFlagDescription)
105111
}
106112

107113
func buildPatch(envKey string, toggleValue bool) string {

cmd/flags/toggle_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,51 @@ func TestToggleOff(t *testing.T) {
332332
assert.Contains(t, string(output), "test-flag")
333333
})
334334

335+
t.Run("passes dryRun query param when --dry-run is set", func(t *testing.T) {
336+
args := []string{
337+
"flags", "toggle-on",
338+
"--access-token", "abcd1234",
339+
"--environment", "test-env",
340+
"--flag", "test-flag",
341+
"--project", "test-proj",
342+
"--output", "json",
343+
"--dry-run",
344+
}
345+
_, err := cmd.CallCmd(
346+
t,
347+
cmd.APIClients{
348+
ResourcesClient: mockClient,
349+
},
350+
analytics.NoopClientFn{}.Tracker(),
351+
args,
352+
)
353+
354+
require.NoError(t, err)
355+
assert.Equal(t, "true", mockClient.Query.Get("dryRun"))
356+
})
357+
358+
t.Run("does not pass dryRun query param by default", func(t *testing.T) {
359+
args := []string{
360+
"flags", "toggle-on",
361+
"--access-token", "abcd1234",
362+
"--environment", "test-env",
363+
"--flag", "test-flag",
364+
"--project", "test-proj",
365+
"--output", "json",
366+
}
367+
_, err := cmd.CallCmd(
368+
t,
369+
cmd.APIClients{
370+
ResourcesClient: mockClient,
371+
},
372+
analytics.NoopClientFn{}.Tracker(),
373+
args,
374+
)
375+
376+
require.NoError(t, err)
377+
assert.Empty(t, mockClient.Query.Get("dryRun"))
378+
})
379+
335380
t.Run("returns error with missing required flags", func(t *testing.T) {
336381
args := []string{
337382
"flags", "toggle-off",

internal/resources/mock_client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
type MockClient struct {
99
Err error
1010
Input []byte
11+
Query url.Values
1112
Response []byte
1213
StatusCode int
1314
}
@@ -21,6 +22,7 @@ func (c *MockClient) MakeRequest(
2122
isBeta bool,
2223
) ([]byte, error) {
2324
c.Input = data
25+
c.Query = query
2426

2527
if c.StatusCode > http.StatusBadRequest {
2628
return c.Response, c.Err

0 commit comments

Comments
 (0)