• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

0xmichalis / nftbk / 18686456443

21 Oct 2025 02:02PM UTC coverage: 44.414% (+4.4%) from 40.026%
18686456443

Pull #83

github

web-flow
Merge f8e420e48 into 106419232
Pull Request #83: feat: add x402 support in the CLI

118 of 206 new or added lines in 6 files covered. (57.28%)

12 existing lines in 1 file now uncovered.

2091 of 4708 relevant lines covered (44.41%)

7.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/cli/mod.rs
1
use std::path::PathBuf;
2

3
use anyhow::Result;
4
use clap::{Parser, Subcommand};
5

6
use crate::logging::LogLevel;
7

8
pub mod commands;
9
pub mod config;
10
pub mod x402;
11

12
#[derive(Parser, Debug)]
13
#[command(author, version, about, long_about = None)]
14
pub struct Cli {
15
    /// Set the log level
16
    #[arg(short, long, value_enum, default_value = "info")]
17
    pub log_level: LogLevel,
18

19
    /// Disable colored log output
20
    #[arg(long, default_value_t = false, action = clap::ArgAction::Set)]
21
    pub no_color: bool,
22

23
    #[command(subcommand)]
24
    pub command: Commands,
25
}
26

27
#[derive(Subcommand, Debug)]
28
pub enum Commands {
29
    /// Create a backup locally
30
    Create {
31
        /// The path to the chains configuration file
32
        #[arg(short = 'c', long, default_value = "config_chains.toml")]
33
        chains_config_path: PathBuf,
34

35
        /// The path to the tokens configuration file
36
        #[arg(short = 't', long, default_value = "config_tokens.toml")]
37
        tokens_config_path: PathBuf,
38

39
        /// The directory to save the backup to
40
        #[arg(short, long, default_value = "nft_backup")]
41
        output_path: Option<PathBuf>,
42

43
        /// Delete redundant files in the backup folder
44
        #[arg(long, default_value_t = false, action = clap::ArgAction::Set)]
45
        prune_redundant: bool,
46

47
        /// Exit on the first error encountered
48
        #[arg(long, default_value_t = false, action = clap::ArgAction::Set)]
49
        exit_on_error: bool,
50

51
        /// Path to a TOML file with IPFS provider configuration
52
        #[arg(long)]
53
        ipfs_config: Option<String>,
54
    },
55
    /// Server-related operations
56
    Server {
57
        #[command(subcommand)]
58
        command: ServerCommands,
59
    },
60
}
61

62
#[derive(Subcommand, Debug)]
63
pub enum ServerCommands {
64
    /// Create a backup on the server
65
    Create {
66
        /// The path to the tokens configuration file
67
        #[arg(short = 't', long, default_value = "config_tokens.toml")]
68
        tokens_config_path: PathBuf,
69

70
        /// The server address to request backups from
71
        #[arg(long, default_value = "http://127.0.0.1:8080")]
72
        server_address: String,
73

74
        /// The directory to save the backup to
75
        #[arg(short, long, default_value = "nft_backup")]
76
        output_path: Option<PathBuf>,
77

78
        /// Force rerunning a completed backup task
79
        #[arg(long, default_value_t = false, action = clap::ArgAction::Set)]
80
        force: bool,
81

82
        /// User-Agent to send to the server (affects archive format)
83
        #[arg(long, default_value = "Linux")]
84
        user_agent: String,
85

86
        /// Request server to pin downloaded assets on IPFS
87
        #[arg(long, default_value_t = false, action = clap::ArgAction::Set)]
88
        pin_on_ipfs: bool,
89

90
        /// Polling interval in milliseconds for checking backup status
91
        #[arg(long, default_value = "10000")]
92
        polling_interval_ms: u64,
93
    },
94
    /// List existing backups on the server
95
    List {
96
        /// The server address to request backups from
97
        #[arg(long, default_value = "http://127.0.0.1:8080")]
98
        server_address: String,
99

100
        /// Show error details in the output table
101
        #[arg(long, default_value_t = false, action = clap::ArgAction::Set)]
102
        show_errors: bool,
103
    },
104
    /// Delete a backup from the server
105
    Delete {
106
        /// The server address to request backups from
107
        #[arg(long, default_value = "http://127.0.0.1:8080")]
108
        server_address: String,
109

110
        /// The task ID of the backup to delete
111
        #[arg(short = 't', long)]
112
        task_id: String,
113

114
        /// Show what would be deleted without actually deleting
115
        #[arg(long, default_value_t = true, action = clap::ArgAction::Set)]
116
        dry_run: bool,
117
    },
118
}
119

120
impl Cli {
121
    pub async fn run(self) -> Result<()> {
×
122
        match self.command {
×
123
            Commands::Create {
124
                chains_config_path,
×
125
                tokens_config_path,
×
126
                output_path,
×
127
                prune_redundant,
×
128
                exit_on_error,
×
129
                ipfs_config,
×
130
            } => {
131
                commands::create::run(
132
                    chains_config_path,
×
133
                    tokens_config_path,
×
134
                    output_path,
×
135
                    prune_redundant,
×
136
                    exit_on_error,
×
137
                    ipfs_config,
×
138
                )
139
                .await
×
140
            }
141
            Commands::Server { command } => match command {
×
142
                ServerCommands::Create {
143
                    tokens_config_path,
×
144
                    server_address,
×
145
                    output_path,
×
146
                    force,
×
147
                    user_agent,
×
148
                    pin_on_ipfs,
×
NEW
149
                    polling_interval_ms,
×
150
                } => {
151
                    commands::server::create::run(
152
                        tokens_config_path,
×
153
                        server_address,
×
154
                        output_path,
×
155
                        force,
×
156
                        user_agent,
×
157
                        pin_on_ipfs,
×
NEW
158
                        Some(polling_interval_ms),
×
159
                    )
160
                    .await
×
161
                }
162
                ServerCommands::List {
163
                    server_address,
×
164
                    show_errors,
×
165
                } => commands::server::list::run(server_address, show_errors).await,
×
166
                ServerCommands::Delete {
NEW
167
                    server_address,
×
NEW
168
                    task_id,
×
NEW
169
                    dry_run,
×
NEW
170
                } => commands::server::delete::run(server_address, task_id, dry_run).await,
×
171
            },
172
        }
173
    }
174
}
175

176
#[cfg(test)]
177
mod tests {
178
    use super::*;
179
    use clap::Parser;
180

181
    mod cli_parsing_tests {
182
        use super::*;
183

184
        #[test]
185
        fn parses_create_command_with_defaults() {
186
            let args = vec!["nftbk-cli", "create"];
187
            let cli = Cli::try_parse_from(args).unwrap();
188

189
            assert_eq!(cli.log_level, LogLevel::Info);
190
            assert!(!cli.no_color);
191
            match cli.command {
192
                Commands::Create {
193
                    chains_config_path,
194
                    tokens_config_path,
195
                    output_path,
196
                    prune_redundant,
197
                    exit_on_error,
198
                    ipfs_config,
199
                } => {
200
                    assert_eq!(chains_config_path, PathBuf::from("config_chains.toml"));
201
                    assert_eq!(tokens_config_path, PathBuf::from("config_tokens.toml"));
202
                    assert_eq!(output_path, Some(PathBuf::from("nft_backup")));
203
                    assert!(!prune_redundant);
204
                    assert!(!exit_on_error);
205
                    assert!(ipfs_config.is_none());
206
                }
207
                _ => panic!("Expected Create command"),
208
            }
209
        }
210

211
        #[test]
212
        fn parses_create_command_with_custom_options() {
213
            let args = vec![
214
                "nftbk-cli",
215
                "--log-level",
216
                "debug",
217
                "--no-color",
218
                "true",
219
                "create",
220
                "--chains-config-path",
221
                "custom_chains.toml",
222
                "--tokens-config-path",
223
                "custom_tokens.toml",
224
                "--output-path",
225
                "/tmp/backup",
226
                "--prune-redundant",
227
                "true",
228
                "--exit-on-error",
229
                "true",
230
                "--ipfs-config",
231
                "ipfs.toml",
232
            ];
233
            let cli = Cli::try_parse_from(args).unwrap();
234

235
            assert_eq!(cli.log_level, LogLevel::Debug);
236
            assert!(cli.no_color);
237
            match cli.command {
238
                Commands::Create {
239
                    chains_config_path,
240
                    tokens_config_path,
241
                    output_path,
242
                    prune_redundant,
243
                    exit_on_error,
244
                    ipfs_config,
245
                } => {
246
                    assert_eq!(chains_config_path, PathBuf::from("custom_chains.toml"));
247
                    assert_eq!(tokens_config_path, PathBuf::from("custom_tokens.toml"));
248
                    assert_eq!(output_path, Some(PathBuf::from("/tmp/backup")));
249
                    assert!(prune_redundant);
250
                    assert!(exit_on_error);
251
                    assert_eq!(ipfs_config, Some("ipfs.toml".to_string()));
252
                }
253
                _ => panic!("Expected Create command"),
254
            }
255
        }
256

257
        #[test]
258
        fn parses_server_create_command_with_defaults() {
259
            let args = vec!["nftbk-cli", "server", "create"];
260
            let cli = Cli::try_parse_from(args).unwrap();
261

262
            match cli.command {
263
                Commands::Server { command } => match command {
264
                    ServerCommands::Create {
265
                        tokens_config_path,
266
                        server_address,
267
                        output_path,
268
                        force,
269
                        user_agent,
270
                        pin_on_ipfs,
271
                        polling_interval_ms,
272
                    } => {
273
                        assert_eq!(tokens_config_path, PathBuf::from("config_tokens.toml"));
274
                        assert_eq!(server_address, "http://127.0.0.1:8080");
275
                        assert_eq!(output_path, Some(PathBuf::from("nft_backup")));
276
                        assert!(!force);
277
                        assert_eq!(user_agent, "Linux");
278
                        assert!(!pin_on_ipfs);
279
                        assert_eq!(polling_interval_ms, 10000);
280
                    }
281
                    _ => panic!("Expected Server Create command"),
282
                },
283
                _ => panic!("Expected Server command"),
284
            }
285
        }
286

287
        #[test]
288
        fn parses_server_create_command_with_custom_options() {
289
            let args = vec![
290
                "nftbk-cli",
291
                "server",
292
                "create",
293
                "--tokens-config-path",
294
                "custom_tokens.toml",
295
                "--server-address",
296
                "https://api.example.com",
297
                "--output-path",
298
                "/tmp/server_backup",
299
                "--force",
300
                "true",
301
                "--user-agent",
302
                "CustomAgent/1.0",
303
                "--pin-on-ipfs",
304
                "true",
305
            ];
306
            let cli = Cli::try_parse_from(args).unwrap();
307

308
            match cli.command {
309
                Commands::Server { command } => match command {
310
                    ServerCommands::Create {
311
                        tokens_config_path,
312
                        server_address,
313
                        output_path,
314
                        force,
315
                        user_agent,
316
                        pin_on_ipfs,
317
                        polling_interval_ms,
318
                    } => {
319
                        assert_eq!(tokens_config_path, PathBuf::from("custom_tokens.toml"));
320
                        assert_eq!(server_address, "https://api.example.com");
321
                        assert_eq!(output_path, Some(PathBuf::from("/tmp/server_backup")));
322
                        assert!(force);
323
                        assert_eq!(user_agent, "CustomAgent/1.0");
324
                        assert!(pin_on_ipfs);
325
                        assert_eq!(polling_interval_ms, 10000); // Default value
326
                    }
327
                    _ => panic!("Expected Server Create command"),
328
                },
329
                _ => panic!("Expected Server command"),
330
            }
331
        }
332

333
        #[test]
334
        fn parses_server_list_command_with_defaults() {
335
            let args = vec!["nftbk-cli", "server", "list"];
336
            let cli = Cli::try_parse_from(args).unwrap();
337

338
            match cli.command {
339
                Commands::Server { command } => match command {
340
                    ServerCommands::List {
341
                        server_address,
342
                        show_errors,
343
                    } => {
344
                        assert_eq!(server_address, "http://127.0.0.1:8080");
345
                        assert!(!show_errors);
346
                    }
347
                    _ => panic!("Expected Server List command"),
348
                },
349
                _ => panic!("Expected Server command"),
350
            }
351
        }
352

353
        #[test]
354
        fn parses_server_list_command_with_custom_server() {
355
            let args = vec![
356
                "nftbk-cli",
357
                "server",
358
                "list",
359
                "--server-address",
360
                "https://api.example.com",
361
            ];
362
            let cli = Cli::try_parse_from(args).unwrap();
363

364
            match cli.command {
365
                Commands::Server { command } => match command {
366
                    ServerCommands::List {
367
                        server_address,
368
                        show_errors,
369
                    } => {
370
                        assert_eq!(server_address, "https://api.example.com");
371
                        assert!(!show_errors);
372
                    }
373
                    _ => panic!("Expected Server List command"),
374
                },
375
                _ => panic!("Expected Server command"),
376
            }
377
        }
378

379
        #[test]
380
        fn parses_server_list_command_with_show_errors() {
381
            let args = vec!["nftbk-cli", "server", "list", "--show-errors", "true"];
382
            let cli = Cli::try_parse_from(args).unwrap();
383

384
            match cli.command {
385
                Commands::Server { command } => match command {
386
                    ServerCommands::List {
387
                        server_address,
388
                        show_errors,
389
                    } => {
390
                        assert_eq!(server_address, "http://127.0.0.1:8080");
391
                        assert!(show_errors);
392
                    }
393
                    _ => panic!("Expected Server List command"),
394
                },
395
                _ => panic!("Expected Server command"),
396
            }
397
        }
398

399
        #[test]
400
        fn parses_server_delete_command_with_defaults() {
401
            let args = vec![
402
                "nftbk-cli",
403
                "server",
404
                "delete",
405
                "--task-id",
406
                "test-task-123",
407
            ];
408
            let cli = Cli::try_parse_from(args).unwrap();
409

410
            match cli.command {
411
                Commands::Server { command } => match command {
412
                    ServerCommands::Delete {
413
                        server_address,
414
                        task_id,
415
                        dry_run,
416
                    } => {
417
                        assert_eq!(server_address, "http://127.0.0.1:8080");
418
                        assert_eq!(task_id, "test-task-123");
419
                        assert!(dry_run);
420
                    }
421
                    _ => panic!("Expected Server Delete command"),
422
                },
423
                _ => panic!("Expected Server command"),
424
            }
425
        }
426

427
        #[test]
428
        fn parses_server_delete_command_with_custom_options() {
429
            let args = vec![
430
                "nftbk-cli",
431
                "server",
432
                "delete",
433
                "--server-address",
434
                "https://api.example.com",
435
                "--task-id",
436
                "custom-task-456",
437
                "--dry-run",
438
                "true",
439
            ];
440
            let cli = Cli::try_parse_from(args).unwrap();
441

442
            match cli.command {
443
                Commands::Server { command } => match command {
444
                    ServerCommands::Delete {
445
                        server_address,
446
                        task_id,
447
                        dry_run,
448
                    } => {
449
                        assert_eq!(server_address, "https://api.example.com");
450
                        assert_eq!(task_id, "custom-task-456");
451
                        assert!(dry_run);
452
                    }
453
                    _ => panic!("Expected Server Delete command"),
454
                },
455
                _ => panic!("Expected Server command"),
456
            }
457
        }
458

459
        #[test]
460
        fn parses_all_log_levels() {
461
            for (level_str, expected_level) in [
462
                ("debug", LogLevel::Debug),
463
                ("info", LogLevel::Info),
464
                ("warn", LogLevel::Warn),
465
                ("error", LogLevel::Error),
466
            ] {
467
                let args = vec!["nftbk-cli", "--log-level", level_str, "create"];
468
                let cli = Cli::try_parse_from(args).unwrap();
469
                assert_eq!(cli.log_level, expected_level);
470
            }
471
        }
472

473
        #[test]
474
        fn handles_no_color_flag() {
475
            let args = vec!["nftbk-cli", "--no-color", "true", "create"];
476
            let cli = Cli::try_parse_from(args).unwrap();
477
            assert!(cli.no_color);
478

479
            let args = vec!["nftbk-cli", "--no-color", "false", "create"];
480
            let cli = Cli::try_parse_from(args).unwrap();
481
            assert!(!cli.no_color);
482
        }
483

484
        #[test]
485
        fn requires_subcommand() {
486
            let args = vec!["nftbk-cli"];
487
            let result = Cli::try_parse_from(args);
488
            assert!(result.is_err());
489
        }
490

491
        #[test]
492
        fn validates_log_level_enum() {
493
            let args = vec!["nftbk-cli", "--log-level", "invalid", "create"];
494
            let result = Cli::try_parse_from(args);
495
            assert!(result.is_err());
496
        }
497
    }
498
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc