Skip to content

Commit dac935c

Browse files
fix(torrent): resume and pause with args (#165)
* fix(torrent): resume with args * fix(torrent): pause with args
1 parent a0663a0 commit dac935c

4 files changed

Lines changed: 129 additions & 25 deletions

File tree

cmd/torrent_pause.go

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,38 @@ import (
1515
func RunTorrentPause() *cobra.Command {
1616
var (
1717
pauseAll bool
18-
names bool
1918
hashes []string
2019
)
2120

2221
var command = &cobra.Command{
2322
Use: "pause",
2423
Short: "Pause specified torrent(s)",
25-
Long: `Pauses torrents indicated by hash, name or a prefix of either. Whitespace indicates next prefix unless argument is surrounded by quotes`,
24+
Long: `Pause the torrent(s) indicated by the supplied hash(es), or pause every torrent with --all.`,
25+
Example: ` qbt torrent pause --all
26+
qbt torrent pause HASH1 HASH2
27+
qbt torrent pause --hashes HASH1,HASH2`,
2628
}
2729

2830
command.Flags().BoolVar(&pauseAll, "all", false, "Pauses all torrents")
2931
command.Flags().StringSliceVar(&hashes, "hashes", []string{}, "Add hashes as comma separated list")
30-
command.Flags().BoolVar(&names, "names", false, "Provided arguments will be read as torrent names")
3132

3233
command.RunE = func(cmd *cobra.Command, args []string) error {
33-
if len(hashes) > 0 {
34+
// Treat positional arguments as hashes too, so `qbt torrent pause HASH` works
35+
// alongside the --hashes flag.
36+
hashes = append(hashes, args...)
37+
38+
if pauseAll {
39+
hashes = []string{"all"}
40+
} else {
41+
if len(hashes) == 0 {
42+
return errors.New("no torrents specified: provide hash(es) as arguments or with --hashes, or use --all")
43+
}
44+
3445
if err := utils.ValidateHash(hashes); err != nil {
3546
return errors.Wrap(err, "invalid hashes supplied")
3647
}
3748
}
49+
3850
config.InitConfig()
3951

4052
qbtSettings := qbittorrent.Config{
@@ -54,19 +66,9 @@ func RunTorrentPause() *cobra.Command {
5466
return errors.Wrap(err, "could not login to qbit")
5567
}
5668

57-
if pauseAll {
58-
hashes = []string{"all"}
59-
}
60-
61-
if len(hashes) == 0 {
62-
log.Printf("No torrents found to pause with provided hashes. Use --all to pause all torrents.")
63-
return nil
64-
}
65-
66-
err := batchRequests(hashes, func(start, end int) error {
69+
if err := batchRequests(hashes, func(start, end int) error {
6770
return qb.PauseCtx(ctx, hashes[start:end])
68-
})
69-
if err != nil {
71+
}); err != nil {
7072
return errors.Wrap(err, "could not pause torrents")
7173
}
7274

cmd/torrent_pause_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package cmd
2+
3+
import (
4+
"io"
5+
"strings"
6+
"testing"
7+
)
8+
9+
// TestRunTorrentPause_requiresTarget mirrors the resume guard (issue #132):
10+
// `qbt torrent pause` with no target must fail loudly instead of silently
11+
// no-opping. Both error paths return before any network call, so the command
12+
// can be exercised without a live qBittorrent.
13+
func TestRunTorrentPause_requiresTarget(t *testing.T) {
14+
tests := []struct {
15+
name string
16+
args []string
17+
wantErr string
18+
}{
19+
{
20+
name: "no target returns error instead of no-op success",
21+
args: []string{},
22+
wantErr: "no torrents specified",
23+
},
24+
{
25+
name: "invalid positional hash is rejected",
26+
args: []string{"not-a-valid-hash"},
27+
wantErr: "invalid hashes supplied",
28+
},
29+
}
30+
31+
for _, tt := range tests {
32+
t.Run(tt.name, func(t *testing.T) {
33+
command := RunTorrentPause()
34+
command.SetArgs(tt.args)
35+
command.SetOut(io.Discard)
36+
command.SetErr(io.Discard)
37+
38+
err := command.Execute()
39+
if err == nil {
40+
t.Fatalf("expected error, got nil")
41+
}
42+
if !strings.Contains(err.Error(), tt.wantErr) {
43+
t.Fatalf("expected error containing %q, got %q", tt.wantErr, err.Error())
44+
}
45+
})
46+
}
47+
}

cmd/torrent_resume.go

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,27 @@ func RunTorrentResume() *cobra.Command {
2222
var command = &cobra.Command{
2323
Use: "resume",
2424
Short: "Resume specified torrent(s)",
25-
Long: `Resumes torrents indicated by hash, name or a prefix of either. Whitespace indicates next prefix unless argument is surrounded by quotes`,
25+
Long: `Resume the torrent(s) indicated by the supplied hash(es), or resume every torrent with --all.`,
26+
Example: ` qbt torrent resume --all
27+
qbt torrent resume HASH1 HASH2
28+
qbt torrent resume --hashes HASH1,HASH2`,
2629
}
2730

2831
command.Flags().BoolVar(&resumeAll, "all", false, "resumes all torrents")
2932
command.Flags().StringSliceVar(&hashes, "hashes", []string{}, "Add hashes as comma separated list")
3033

3134
command.RunE = func(cmd *cobra.Command, args []string) error {
32-
if len(hashes) > 0 {
35+
// Treat positional arguments as hashes too, so `qbt torrent resume HASH` works
36+
// alongside the --hashes flag.
37+
hashes = append(hashes, args...)
38+
39+
if resumeAll {
40+
hashes = []string{"all"}
41+
} else {
42+
if len(hashes) == 0 {
43+
return errors.New("no torrents specified: provide hash(es) as arguments or with --hashes, or use --all")
44+
}
45+
3346
if err := utils.ValidateHash(hashes); err != nil {
3447
return errors.Wrap(err, "invalid hashes supplied")
3548
}
@@ -54,14 +67,9 @@ func RunTorrentResume() *cobra.Command {
5467
return errors.Wrap(err, "could not login to qbit")
5568
}
5669

57-
if resumeAll {
58-
hashes = []string{"all"}
59-
}
60-
61-
err := batchRequests(hashes, func(start, end int) error {
70+
if err := batchRequests(hashes, func(start, end int) error {
6271
return qb.ResumeCtx(ctx, hashes[start:end])
63-
})
64-
if err != nil {
72+
}); err != nil {
6573
return errors.Wrap(err, "could not resume torrents")
6674
}
6775

cmd/torrent_resume_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package cmd
2+
3+
import (
4+
"io"
5+
"strings"
6+
"testing"
7+
)
8+
9+
// TestRunTorrentResume_requiresTarget guards against the false-positive reported
10+
// in issue #132: `qbt torrent resume` with no target must fail loudly instead of
11+
// printing "torrent(s) successfully resumed". Both error paths return before any
12+
// network call, so the command can be exercised without a live qBittorrent.
13+
func TestRunTorrentResume_requiresTarget(t *testing.T) {
14+
tests := []struct {
15+
name string
16+
args []string
17+
wantErr string
18+
}{
19+
{
20+
name: "no target returns error instead of false-positive success",
21+
args: []string{},
22+
wantErr: "no torrents specified",
23+
},
24+
{
25+
name: "invalid positional hash is rejected",
26+
args: []string{"not-a-valid-hash"},
27+
wantErr: "invalid hashes supplied",
28+
},
29+
}
30+
31+
for _, tt := range tests {
32+
t.Run(tt.name, func(t *testing.T) {
33+
command := RunTorrentResume()
34+
command.SetArgs(tt.args)
35+
command.SetOut(io.Discard)
36+
command.SetErr(io.Discard)
37+
38+
err := command.Execute()
39+
if err == nil {
40+
t.Fatalf("expected error, got nil")
41+
}
42+
if !strings.Contains(err.Error(), tt.wantErr) {
43+
t.Fatalf("expected error containing %q, got %q", tt.wantErr, err.Error())
44+
}
45+
})
46+
}
47+
}

0 commit comments

Comments
 (0)