From 8f7690e6ed76a2a9af9c91725748aad746bdd678 Mon Sep 17 00:00:00 2001 From: Kush Date: Tue, 16 Jun 2026 14:11:45 -0400 Subject: [PATCH] feat(recording): add pause and resume subcommands Expose the Update Recording Voice API endpoint (PUT /accounts/{accountId}/calls/{callId}/recording) via: band recording pause -> {"state":"paused"} band recording resume -> {"state":"recording"} Closes a CLI/API parity gap. No REST stop exists for this endpoint (StopRecording is BXML-verb-only), so only pause and resume are added. --- cmd/recording/golden_test.go | 46 +++++++++++++++++++++++++++++++++ cmd/recording/pause.go | 42 ++++++++++++++++++++++++++++++ cmd/recording/recording_test.go | 14 +++++++++- cmd/recording/resume.go | 42 ++++++++++++++++++++++++++++++ 4 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 cmd/recording/pause.go create mode 100644 cmd/recording/resume.go diff --git a/cmd/recording/golden_test.go b/cmd/recording/golden_test.go index ac3f0b4..e6870ed 100644 --- a/cmd/recording/golden_test.go +++ b/cmd/recording/golden_test.go @@ -44,3 +44,49 @@ func TestRecordingListPlainOutput(t *testing.T) { t.Fatalf("golden mismatch:\n got: %q\nwant: %q", out, want) } } + +func TestRecordingPauseOutput(t *testing.T) { + // No t.Parallel(): these tests mutate the global cmdutil.VoiceClient. + orig := cmdutil.VoiceClient + t.Cleanup(func() { cmdutil.VoiceClient = orig }) + cmdutil.VoiceClient = func(string) (api.Requester, string, error) { + return &testutil.FakeClient{}, "acct-123", nil + } + + root := testutil.NewTestRoot(pauseCmd) + root.SetArgs([]string{"pause", "c-abc123"}) + + out := testutil.CaptureStdout(t, func() { + if err := root.Execute(); err != nil { + t.Fatalf("execute: %v", err) + } + }) + + want := "Recording paused on call c-abc123.\n" + if out != want { + t.Fatalf("golden mismatch:\n got: %q\nwant: %q", out, want) + } +} + +func TestRecordingResumeOutput(t *testing.T) { + // No t.Parallel(): these tests mutate the global cmdutil.VoiceClient. + orig := cmdutil.VoiceClient + t.Cleanup(func() { cmdutil.VoiceClient = orig }) + cmdutil.VoiceClient = func(string) (api.Requester, string, error) { + return &testutil.FakeClient{}, "acct-123", nil + } + + root := testutil.NewTestRoot(resumeCmd) + root.SetArgs([]string{"resume", "c-abc123"}) + + out := testutil.CaptureStdout(t, func() { + if err := root.Execute(); err != nil { + t.Fatalf("execute: %v", err) + } + }) + + want := "Recording resumed on call c-abc123.\n" + if out != want { + t.Fatalf("golden mismatch:\n got: %q\nwant: %q", out, want) + } +} diff --git a/cmd/recording/pause.go b/cmd/recording/pause.go new file mode 100644 index 0000000..8fda7c0 --- /dev/null +++ b/cmd/recording/pause.go @@ -0,0 +1,42 @@ +package recording + +import ( + "fmt" + "net/url" + + "github.com/spf13/cobra" + + "github.com/Bandwidth/cli/internal/cmdutil" +) + +func init() { + Cmd.AddCommand(pauseCmd) +} + +var pauseCmd = &cobra.Command{ + Use: "pause ", + Short: "Pause the active recording on a live call", + Args: cobra.ExactArgs(1), + RunE: runPause, +} + +func runPause(cmd *cobra.Command, args []string) error { + if err := cmdutil.ValidateID(args[0]); err != nil { + return err + } + client, acctID, err := cmdutil.VoiceClient(cmdutil.AccountIDFlag(cmd)) + if err != nil { + return err + } + + reqBody := map[string]string{ + "state": "paused", + } + + if err := client.Put(fmt.Sprintf("/accounts/%s/calls/%s/recording", acctID, url.PathEscape(args[0])), reqBody, nil); err != nil { + return fmt.Errorf("pausing recording: %w", err) + } + + fmt.Printf("Recording paused on call %s.\n", args[0]) + return nil +} diff --git a/cmd/recording/recording_test.go b/cmd/recording/recording_test.go index 911086d..e0a581e 100644 --- a/cmd/recording/recording_test.go +++ b/cmd/recording/recording_test.go @@ -13,7 +13,7 @@ func TestCmdStructure(t *testing.T) { for _, c := range Cmd.Commands() { subs[c.Use] = true } - for _, name := range []string{"get ", "list ", "delete ", "download "} { + for _, name := range []string{"get ", "list ", "delete ", "download ", "pause ", "resume "} { if !subs[name] { t.Errorf("missing subcommand %q", name) } @@ -38,3 +38,15 @@ func TestDownloadRequiredFlags(t *testing.T) { t.Error("missing flag \"output\"") } } + +func TestPauseArgs(t *testing.T) { + if pauseCmd.Args == nil { + t.Fatal("pause command should have arg validation") + } +} + +func TestResumeArgs(t *testing.T) { + if resumeCmd.Args == nil { + t.Fatal("resume command should have arg validation") + } +} diff --git a/cmd/recording/resume.go b/cmd/recording/resume.go new file mode 100644 index 0000000..67a86bc --- /dev/null +++ b/cmd/recording/resume.go @@ -0,0 +1,42 @@ +package recording + +import ( + "fmt" + "net/url" + + "github.com/spf13/cobra" + + "github.com/Bandwidth/cli/internal/cmdutil" +) + +func init() { + Cmd.AddCommand(resumeCmd) +} + +var resumeCmd = &cobra.Command{ + Use: "resume ", + Short: "Resume a paused recording on a live call", + Args: cobra.ExactArgs(1), + RunE: runResume, +} + +func runResume(cmd *cobra.Command, args []string) error { + if err := cmdutil.ValidateID(args[0]); err != nil { + return err + } + client, acctID, err := cmdutil.VoiceClient(cmdutil.AccountIDFlag(cmd)) + if err != nil { + return err + } + + reqBody := map[string]string{ + "state": "recording", + } + + if err := client.Put(fmt.Sprintf("/accounts/%s/calls/%s/recording", acctID, url.PathEscape(args[0])), reqBody, nil); err != nil { + return fmt.Errorf("resuming recording: %w", err) + } + + fmt.Printf("Recording resumed on call %s.\n", args[0]) + return nil +}