Skip to main content

alimentar/cli/
registry.rs

1//! Registry CLI commands for dataset sharing and discovery.
2
3use std::path::{Path, PathBuf};
4
5use clap::Subcommand;
6
7use super::basic::load_dataset;
8use crate::{
9    backend::LocalBackend,
10    registry::{DatasetMetadata, Registry},
11    Dataset,
12};
13
14/// Registry commands for dataset sharing and discovery.
15#[derive(Subcommand, Debug)]
16pub enum RegistryCommands {
17    /// Initialize a new registry
18    Init {
19        /// Path to registry directory
20        #[arg(short, long, default_value = ".alimentar")]
21        path: PathBuf,
22    },
23    /// List all datasets in a registry
24    List {
25        /// Path to registry directory
26        #[arg(short, long, default_value = ".alimentar")]
27        path: PathBuf,
28    },
29    /// Push (publish) a dataset to the registry
30    /// apr propagates its auto `--version` into every subcommand
31    /// (`propagate_version = true`), which collides with this real
32    /// `--version` argument. Nothing wants `apr data x registry push
33    /// --version` to print apr's version, so disable the auto flag here.
34    #[command(disable_version_flag = true)]
35    Push {
36        /// Path to the dataset file (parquet)
37        input: PathBuf,
38        /// Dataset name in the registry
39        #[arg(short, long)]
40        name: String,
41        /// Dataset version (semver)
42        // Long-only: apr propagates its global -v/--verbose and -q/--quiet
43        // into every subcommand, so a derived short here makes the whole
44        // clap tree invalid and the subcommand panics on any invocation.
45        #[arg(long, default_value = "1.0.0")]
46        version: String,
47        /// Description of the dataset
48        #[arg(short, long, default_value = "")]
49        description: String,
50        /// License identifier (e.g., MIT, Apache-2.0)
51        #[arg(short, long, default_value = "")]
52        license: String,
53        /// Tags for the dataset (comma-separated)
54        #[arg(short, long, default_value = "")]
55        tags: String,
56        /// Path to registry directory
57        #[arg(long, default_value = ".alimentar")]
58        registry: PathBuf,
59    },
60    /// Pull (download) a dataset from the registry
61    /// apr propagates its auto `--version` into every subcommand
62    /// (`propagate_version = true`), which collides with this real
63    /// `--version` argument. Nothing wants `apr data x registry push
64    /// --version` to print apr's version, so disable the auto flag here.
65    #[command(disable_version_flag = true)]
66    Pull {
67        /// Dataset name
68        name: String,
69        /// Output path for the dataset
70        #[arg(short, long)]
71        output: PathBuf,
72        /// Specific version to pull (defaults to latest)
73        // Long-only: apr propagates its global -v/--verbose and -q/--quiet
74        // into every subcommand, so a derived short here makes the whole
75        // clap tree invalid and the subcommand panics on any invocation.
76        #[arg(long)]
77        version: Option<String>,
78        /// Path to registry directory
79        #[arg(long, default_value = ".alimentar")]
80        registry: PathBuf,
81    },
82    /// Search datasets by name or description
83    Search {
84        /// Search query
85        query: String,
86        /// Path to registry directory
87        #[arg(short, long, default_value = ".alimentar")]
88        path: PathBuf,
89    },
90    /// Show detailed info about a specific dataset
91    ShowInfo {
92        /// Dataset name
93        name: String,
94        /// Path to registry directory
95        #[arg(short, long, default_value = ".alimentar")]
96        path: PathBuf,
97    },
98    /// Delete a dataset version from the registry
99    /// apr propagates its auto `--version` into every subcommand
100    /// (`propagate_version = true`), which collides with this real
101    /// `--version` argument. Nothing wants `apr data x registry push
102    /// --version` to print apr's version, so disable the auto flag here.
103    #[command(disable_version_flag = true)]
104    Delete {
105        /// Dataset name
106        name: String,
107        /// Version to delete
108        // Long-only: apr propagates its global -v/--verbose and -q/--quiet
109        // into every subcommand, so a derived short here makes the whole
110        // clap tree invalid and the subcommand panics on any invocation.
111        #[arg(long)]
112        version: String,
113        /// Path to registry directory
114        #[arg(short, long, default_value = ".alimentar")]
115        path: PathBuf,
116    },
117}
118
119/// Create a registry with the given path.
120pub(crate) fn create_registry(path: &Path) -> crate::Result<Registry> {
121    // Ensure directory exists
122    if !path.exists() {
123        std::fs::create_dir_all(path).map_err(|e| crate::Error::io(e, path))?;
124    }
125    let backend = LocalBackend::new(path)?;
126    Ok(Registry::new(Box::new(backend)))
127}
128
129/// Initialize a new registry.
130pub(crate) fn cmd_registry_init(path: &Path) -> crate::Result<()> {
131    let registry = create_registry(path)?;
132    registry.init()?;
133    println!("Initialized registry at: {}", path.display());
134    Ok(())
135}
136
137/// List all datasets in a registry.
138pub(crate) fn cmd_registry_list(path: &Path) -> crate::Result<()> {
139    let registry = create_registry(path)?;
140    let datasets = registry.list()?;
141
142    if datasets.is_empty() {
143        println!("No datasets in registry.");
144        return Ok(());
145    }
146
147    println!("Datasets in registry:\n");
148    println!(
149        "{:<25} {:<12} {:<10} {:<15} DESCRIPTION",
150        "NAME", "LATEST", "VERSIONS", "ROWS"
151    );
152    println!("{}", "-".repeat(80));
153
154    for ds in datasets {
155        let desc = if ds.metadata.description.len() > 30 {
156            format!("{}...", &ds.metadata.description[..27])
157        } else {
158            ds.metadata.description.clone()
159        };
160        println!(
161            "{:<25} {:<12} {:<10} {:<15} {}",
162            ds.name,
163            ds.latest,
164            ds.versions.len(),
165            ds.num_rows,
166            desc
167        );
168    }
169
170    Ok(())
171}
172
173/// Push (publish) a dataset to the registry.
174#[allow(clippy::too_many_arguments)]
175pub(crate) fn cmd_registry_push(
176    input: &Path,
177    name: &str,
178    version: &str,
179    description: &str,
180    license: &str,
181    tags: &str,
182    registry_path: &Path,
183) -> crate::Result<()> {
184    let registry = create_registry(registry_path)?;
185
186    // Initialize if needed
187    registry.init()?;
188
189    // Load the dataset
190    let dataset = load_dataset(input)?;
191
192    // Parse tags
193    let tag_list: Vec<String> = if tags.is_empty() {
194        Vec::new()
195    } else {
196        tags.split(',').map(|s| s.trim().to_string()).collect()
197    };
198
199    // Create metadata
200    let metadata = DatasetMetadata {
201        description: description.to_string(),
202        license: license.to_string(),
203        tags: tag_list,
204        source: Some(input.display().to_string()),
205        citation: None,
206        sha256: None, // Computed during save, not at publish time
207    };
208
209    // Publish
210    registry.publish(name, version, &dataset, metadata)?;
211
212    println!(
213        "Published {}@{} ({} rows) to registry",
214        name,
215        version,
216        dataset.len()
217    );
218
219    Ok(())
220}
221
222/// Pull (download) a dataset from the registry.
223pub(crate) fn cmd_registry_pull(
224    name: &str,
225    output: &Path,
226    version: Option<&str>,
227    registry_path: &Path,
228) -> crate::Result<()> {
229    let registry = create_registry(registry_path)?;
230
231    // Pull the dataset
232    let dataset = registry.pull(name, version)?;
233
234    // Save to output
235    dataset.to_parquet(output)?;
236
237    let ver = version.unwrap_or("latest");
238    println!(
239        "Pulled {}@{} ({} rows) to {}",
240        name,
241        ver,
242        dataset.len(),
243        output.display()
244    );
245
246    Ok(())
247}
248
249/// Search datasets by name or description.
250pub(crate) fn cmd_registry_search(query: &str, path: &Path) -> crate::Result<()> {
251    let registry = create_registry(path)?;
252    let results = registry.search(query)?;
253
254    if results.is_empty() {
255        println!("No datasets found matching '{}'", query);
256        return Ok(());
257    }
258
259    println!("Search results for '{}':\n", query);
260    println!("{:<25} {:<12} {:<10} DESCRIPTION", "NAME", "LATEST", "ROWS");
261    println!("{}", "-".repeat(70));
262
263    for ds in results {
264        let desc = if ds.metadata.description.len() > 30 {
265            format!("{}...", &ds.metadata.description[..27])
266        } else {
267            ds.metadata.description.clone()
268        };
269        println!(
270            "{:<25} {:<12} {:<10} {}",
271            ds.name, ds.latest, ds.num_rows, desc
272        );
273    }
274
275    Ok(())
276}
277
278/// Show detailed info about a specific dataset.
279pub(crate) fn cmd_registry_show_info(name: &str, path: &Path) -> crate::Result<()> {
280    let registry = create_registry(path)?;
281    let info = registry.get_info(name)?;
282
283    println!("Dataset: {}", info.name);
284    println!("Latest: {}", info.latest);
285    println!("Versions: {}", info.versions.join(", "));
286    println!("Rows: {}", info.num_rows);
287    println!("Size: {} bytes", info.size_bytes);
288    println!();
289    println!("Description: {}", info.metadata.description);
290    println!("License: {}", info.metadata.license);
291    println!("Tags: {}", info.metadata.tags.join(", "));
292
293    if let Some(source) = &info.metadata.source {
294        println!("Source: {}", source);
295    }
296    if let Some(citation) = &info.metadata.citation {
297        println!("Citation: {}", citation);
298    }
299
300    println!();
301    println!("Schema:");
302    if let Some(fields) = info.schema.get("fields").and_then(|f| f.as_array()) {
303        for field in fields {
304            let name = field.get("name").and_then(|n| n.as_str()).unwrap_or("?");
305            let dtype = field
306                .get("data_type")
307                .and_then(|d| d.as_str())
308                .unwrap_or("?");
309            let nullable = field
310                .get("nullable")
311                .and_then(serde_json::Value::as_bool)
312                .unwrap_or(true);
313            let null_str = if nullable { "nullable" } else { "not null" };
314            println!("  - {} ({}) [{}]", name, dtype, null_str);
315        }
316    }
317
318    Ok(())
319}
320
321/// Delete a dataset version from the registry.
322pub(crate) fn cmd_registry_delete(name: &str, version: &str, path: &Path) -> crate::Result<()> {
323    let registry = create_registry(path)?;
324    registry.delete(name, version)?;
325    println!("Deleted {}@{} from registry", name, version);
326    Ok(())
327}
328
329#[cfg(test)]
330#[allow(
331    clippy::cast_possible_truncation,
332    clippy::cast_possible_wrap,
333    clippy::cast_precision_loss,
334    clippy::uninlined_format_args,
335    clippy::unwrap_used,
336    clippy::expect_used,
337    clippy::redundant_clone,
338    clippy::cast_lossless,
339    clippy::redundant_closure_for_method_calls,
340    clippy::too_many_lines,
341    clippy::float_cmp,
342    clippy::similar_names,
343    clippy::needless_late_init,
344    clippy::redundant_pattern_matching
345)]
346mod tests {
347    use std::sync::Arc;
348
349    use arrow::{
350        array::{Int32Array, StringArray},
351        datatypes::{DataType, Field, Schema},
352    };
353
354    use super::*;
355    use crate::ArrowDataset;
356
357    fn create_test_parquet(path: &Path, rows: usize) {
358        let schema = Arc::new(Schema::new(vec![
359            Field::new("id", DataType::Int32, false),
360            Field::new("name", DataType::Utf8, false),
361        ]));
362
363        let ids: Vec<i32> = (0..rows as i32).collect();
364        let names: Vec<String> = ids.iter().map(|i| format!("item_{}", i)).collect();
365
366        let batch = arrow::array::RecordBatch::try_new(
367            schema,
368            vec![
369                Arc::new(Int32Array::from(ids)),
370                Arc::new(StringArray::from(names)),
371            ],
372        )
373        .ok()
374        .unwrap_or_else(|| panic!("Should create batch"));
375
376        let dataset = ArrowDataset::from_batch(batch)
377            .ok()
378            .unwrap_or_else(|| panic!("Should create dataset"));
379
380        dataset
381            .to_parquet(path)
382            .ok()
383            .unwrap_or_else(|| panic!("Should write parquet"));
384    }
385
386    #[test]
387    fn test_cmd_registry_init() {
388        let temp_dir = tempfile::tempdir()
389            .ok()
390            .unwrap_or_else(|| panic!("Should create temp dir"));
391        let registry_path = temp_dir.path().join("registry");
392
393        let result = cmd_registry_init(&registry_path);
394        assert!(result.is_ok());
395        assert!(registry_path.exists());
396    }
397
398    #[test]
399    fn test_cmd_registry_list_empty() {
400        let temp_dir = tempfile::tempdir()
401            .ok()
402            .unwrap_or_else(|| panic!("Should create temp dir"));
403        let registry_path = temp_dir.path().join("registry");
404
405        // Init first
406        cmd_registry_init(&registry_path)
407            .ok()
408            .unwrap_or_else(|| panic!("Should init"));
409
410        let result = cmd_registry_list(&registry_path);
411        assert!(result.is_ok());
412    }
413
414    #[test]
415    fn test_cmd_registry_push_and_pull() {
416        let temp_dir = tempfile::tempdir()
417            .ok()
418            .unwrap_or_else(|| panic!("Should create temp dir"));
419        let registry_path = temp_dir.path().join("registry");
420        let input = temp_dir.path().join("data.parquet");
421        let output = temp_dir.path().join("pulled.parquet");
422
423        // Create test data
424        create_test_parquet(&input, 25);
425
426        // Push
427        let result = cmd_registry_push(
428            &input,
429            "test-dataset",
430            "1.0.0",
431            "A test dataset",
432            "MIT",
433            "test,example",
434            &registry_path,
435        );
436        assert!(result.is_ok());
437
438        // List should show the dataset
439        let result = cmd_registry_list(&registry_path);
440        assert!(result.is_ok());
441
442        // Pull
443        let result = cmd_registry_pull("test-dataset", &output, Some("1.0.0"), &registry_path);
444        assert!(result.is_ok());
445        assert!(output.exists());
446
447        // Verify data
448        let original = ArrowDataset::from_parquet(&input)
449            .ok()
450            .unwrap_or_else(|| panic!("Should load original"));
451        let pulled = ArrowDataset::from_parquet(&output)
452            .ok()
453            .unwrap_or_else(|| panic!("Should load pulled"));
454        assert_eq!(original.len(), pulled.len());
455    }
456
457    #[test]
458    fn test_cmd_registry_search() {
459        let temp_dir = tempfile::tempdir()
460            .ok()
461            .unwrap_or_else(|| panic!("Should create temp dir"));
462        let registry_path = temp_dir.path().join("registry");
463        let input = temp_dir.path().join("data.parquet");
464
465        create_test_parquet(&input, 10);
466
467        // Push with description
468        cmd_registry_push(
469            &input,
470            "ml-dataset",
471            "1.0.0",
472            "Machine learning training data",
473            "Apache-2.0",
474            "ml,training",
475            &registry_path,
476        )
477        .ok()
478        .unwrap_or_else(|| panic!("Should push"));
479
480        // Search by name
481        let result = cmd_registry_search("ml", &registry_path);
482        assert!(result.is_ok());
483
484        // Search by description
485        let result = cmd_registry_search("machine", &registry_path);
486        assert!(result.is_ok());
487    }
488
489    #[test]
490    fn test_cmd_registry_show_info() {
491        let temp_dir = tempfile::tempdir()
492            .ok()
493            .unwrap_or_else(|| panic!("Should create temp dir"));
494        let registry_path = temp_dir.path().join("registry");
495        let input = temp_dir.path().join("data.parquet");
496
497        create_test_parquet(&input, 10);
498
499        cmd_registry_push(
500            &input,
501            "info-test",
502            "1.0.0",
503            "Test description",
504            "MIT",
505            "test",
506            &registry_path,
507        )
508        .ok()
509        .unwrap_or_else(|| panic!("Should push"));
510
511        let result = cmd_registry_show_info("info-test", &registry_path);
512        assert!(result.is_ok());
513    }
514
515    #[test]
516    fn test_cmd_registry_delete() {
517        let temp_dir = tempfile::tempdir()
518            .ok()
519            .unwrap_or_else(|| panic!("Should create temp dir"));
520        let registry_path = temp_dir.path().join("registry");
521        let input = temp_dir.path().join("data.parquet");
522
523        create_test_parquet(&input, 10);
524
525        // Push
526        cmd_registry_push(
527            &input,
528            "delete-test",
529            "1.0.0",
530            "Will be deleted",
531            "",
532            "",
533            &registry_path,
534        )
535        .ok()
536        .unwrap_or_else(|| panic!("Should push"));
537
538        // Delete
539        let result = cmd_registry_delete("delete-test", "1.0.0", &registry_path);
540        assert!(result.is_ok());
541
542        // Should no longer exist
543        let result = cmd_registry_show_info("delete-test", &registry_path);
544        assert!(result.is_err());
545    }
546
547    #[test]
548    fn test_cmd_registry_pull_latest() {
549        let temp_dir = tempfile::tempdir()
550            .ok()
551            .unwrap_or_else(|| panic!("Should create temp dir"));
552        let registry_path = temp_dir.path().join("registry");
553        let input1 = temp_dir.path().join("v1.parquet");
554        let input2 = temp_dir.path().join("v2.parquet");
555        let output = temp_dir.path().join("pulled.parquet");
556
557        create_test_parquet(&input1, 10);
558        create_test_parquet(&input2, 20);
559
560        // Push v1
561        cmd_registry_push(&input1, "versioned", "1.0.0", "V1", "", "", &registry_path)
562            .ok()
563            .unwrap_or_else(|| panic!("Should push v1"));
564
565        // Push v2
566        cmd_registry_push(&input2, "versioned", "2.0.0", "V2", "", "", &registry_path)
567            .ok()
568            .unwrap_or_else(|| panic!("Should push v2"));
569
570        // Pull latest (no version specified)
571        let result = cmd_registry_pull("versioned", &output, None, &registry_path);
572        assert!(result.is_ok());
573
574        // Should be v2 (20 rows)
575        let pulled = ArrowDataset::from_parquet(&output)
576            .ok()
577            .unwrap_or_else(|| panic!("Should load"));
578        assert_eq!(pulled.len(), 20);
579    }
580
581    #[test]
582    fn test_cmd_registry_search_no_results() {
583        let temp_dir = tempfile::tempdir()
584            .ok()
585            .unwrap_or_else(|| panic!("Should create temp dir"));
586        let registry_path = temp_dir.path().join("registry");
587
588        // Init registry
589        cmd_registry_init(&registry_path)
590            .ok()
591            .unwrap_or_else(|| panic!("Should init"));
592
593        // Search for something that doesn't exist
594        let result = cmd_registry_search("nonexistent-dataset-xyz", &registry_path);
595        assert!(result.is_ok());
596    }
597
598    #[test]
599    fn test_cmd_registry_push_with_long_description() {
600        let temp_dir = tempfile::tempdir()
601            .ok()
602            .unwrap_or_else(|| panic!("Should create temp dir"));
603        let registry_path = temp_dir.path().join("registry");
604        let input = temp_dir.path().join("data.parquet");
605
606        create_test_parquet(&input, 10);
607
608        // Push with a very long description
609        let long_desc = "This is a very long description that exceeds thirty characters and will be truncated in the list view";
610        let result = cmd_registry_push(
611            &input,
612            "long-desc-test",
613            "1.0.0",
614            long_desc,
615            "MIT",
616            "",
617            &registry_path,
618        );
619        assert!(result.is_ok());
620
621        // List should truncate the description
622        let result = cmd_registry_list(&registry_path);
623        assert!(result.is_ok());
624    }
625
626    #[test]
627    fn test_cmd_registry_show_info_with_all_metadata() {
628        let temp_dir = tempfile::tempdir()
629            .ok()
630            .unwrap_or_else(|| panic!("Should create temp dir"));
631        let registry_path = temp_dir.path().join("registry");
632        let input = temp_dir.path().join("data.parquet");
633
634        create_test_parquet(&input, 10);
635
636        // Push with all metadata
637        cmd_registry_push(
638            &input,
639            "full-metadata",
640            "1.0.0",
641            "Full metadata test",
642            "Apache-2.0",
643            "test,metadata,full",
644            &registry_path,
645        )
646        .ok()
647        .unwrap_or_else(|| panic!("Should push"));
648
649        let result = cmd_registry_show_info("full-metadata", &registry_path);
650        assert!(result.is_ok());
651    }
652
653    #[test]
654    fn test_create_registry_new_directory() {
655        let temp_dir = tempfile::tempdir()
656            .ok()
657            .unwrap_or_else(|| panic!("Should create temp dir"));
658        let registry_path = temp_dir.path().join("new_registry_dir");
659
660        // Directory doesn't exist yet
661        assert!(!registry_path.exists());
662
663        let result = create_registry(&registry_path);
664        assert!(result.is_ok());
665
666        // Directory should now exist
667        assert!(registry_path.exists());
668    }
669
670    #[test]
671    fn test_cmd_registry_delete_nonexistent() {
672        let temp_dir = tempfile::tempdir()
673            .ok()
674            .unwrap_or_else(|| panic!("Should create temp dir"));
675        let registry_path = temp_dir.path().join("registry");
676
677        // Init registry
678        cmd_registry_init(&registry_path)
679            .ok()
680            .unwrap_or_else(|| panic!("Should init"));
681
682        // Try to delete something that doesn't exist
683        let result = cmd_registry_delete("nonexistent", "1.0.0", &registry_path);
684        assert!(result.is_err());
685    }
686
687    #[test]
688    fn test_cmd_registry_pull_nonexistent() {
689        let temp_dir = tempfile::tempdir()
690            .ok()
691            .unwrap_or_else(|| panic!("Should create temp dir"));
692        let registry_path = temp_dir.path().join("registry");
693        let output = temp_dir.path().join("output.parquet");
694
695        // Init registry
696        cmd_registry_init(&registry_path)
697            .ok()
698            .unwrap_or_else(|| panic!("Should init"));
699
700        // Try to pull something that doesn't exist
701        let result = cmd_registry_pull("nonexistent", &output, None, &registry_path);
702        assert!(result.is_err());
703    }
704
705    #[test]
706    fn test_cmd_registry_search_with_data() {
707        let temp_dir = tempfile::tempdir()
708            .ok()
709            .unwrap_or_else(|| panic!("Should create temp dir"));
710        let registry_path = temp_dir.path().join("registry");
711        let data_path = temp_dir.path().join("data.parquet");
712
713        create_test_parquet(&data_path, 20);
714
715        // Push a dataset first
716        cmd_registry_push(
717            &data_path,
718            "searchable-data",
719            "1.0.0",
720            "Dataset for search test",
721            "MIT",
722            "search,test",
723            &registry_path,
724        )
725        .unwrap();
726
727        let result = cmd_registry_search("search", &registry_path);
728        assert!(result.is_ok());
729    }
730
731    #[test]
732    fn test_cmd_registry_search_empty_results() {
733        let temp_dir = tempfile::tempdir()
734            .ok()
735            .unwrap_or_else(|| panic!("Should create temp dir"));
736        let registry_path = temp_dir.path().join("registry");
737
738        cmd_registry_init(&registry_path).unwrap();
739
740        let result = cmd_registry_search("nonexistent", &registry_path);
741        assert!(result.is_ok());
742    }
743
744    #[test]
745    fn test_cmd_registry_show_info_basic() {
746        let temp_dir = tempfile::tempdir()
747            .ok()
748            .unwrap_or_else(|| panic!("Should create temp dir"));
749        let registry_path = temp_dir.path().join("registry");
750        let data_path = temp_dir.path().join("data.parquet");
751
752        create_test_parquet(&data_path, 20);
753
754        cmd_registry_push(
755            &data_path,
756            "info-dataset",
757            "1.0.0",
758            "Dataset for info test",
759            "Apache-2.0",
760            "info,test",
761            &registry_path,
762        )
763        .unwrap();
764
765        let result = cmd_registry_show_info("info-dataset", &registry_path);
766        assert!(result.is_ok());
767    }
768
769    #[test]
770    fn test_cmd_registry_show_info_not_found() {
771        let temp_dir = tempfile::tempdir()
772            .ok()
773            .unwrap_or_else(|| panic!("Should create temp dir"));
774        let registry_path = temp_dir.path().join("registry");
775
776        cmd_registry_init(&registry_path).unwrap();
777
778        let result = cmd_registry_show_info("nonexistent", &registry_path);
779        assert!(result.is_err());
780    }
781
782    #[test]
783    fn test_cmd_registry_delete_existing() {
784        let temp_dir = tempfile::tempdir()
785            .ok()
786            .unwrap_or_else(|| panic!("Should create temp dir"));
787        let registry_path = temp_dir.path().join("registry");
788        let data_path = temp_dir.path().join("data.parquet");
789
790        create_test_parquet(&data_path, 20);
791
792        cmd_registry_push(
793            &data_path,
794            "delete-test",
795            "1.0.0",
796            "Dataset to delete",
797            "MIT",
798            "delete,test",
799            &registry_path,
800        )
801        .unwrap();
802
803        let result = cmd_registry_delete("delete-test", "1.0.0", &registry_path);
804        assert!(result.is_ok());
805    }
806}