use faucet_cli::cli::{InitArgs, PreviewArgs};
use faucet_cli::commands::{init, preview};
use faucet_cli::error::CliError;
use std::path::PathBuf;
fn init_args(output: PathBuf, source: Option<&str>, sink: Option<&str>) -> InitArgs {
InitArgs {
name: Some("smoke-pipeline".to_string()),
source: source.map(str::to_string),
sink: sink.map(str::to_string),
output,
force: false,
interactive: false,
template: "default".to_string(),
discover: false,
executable: None,
stream: None,
}
}
#[cfg(all(feature = "source-csv", feature = "sink-jsonl"))]
#[tokio::test]
async fn init_renders_csv_to_jsonl_template_with_required_markers() {
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("pipeline.yaml");
init::run(init_args(out.clone(), Some("csv"), Some("jsonl")))
.await
.expect("init should write the scaffold");
let body = std::fs::read_to_string(&out).unwrap();
assert!(body.starts_with("version: 1\n"), "{body}");
assert!(body.contains("name: smoke-pipeline\n"), "{body}");
assert!(body.contains(" sources:\n"), "{body}");
assert!(body.contains(" default:\n"), "{body}");
assert!(body.contains(" type: csv\n"), "{body}");
assert!(body.contains(" sinks:\n"), "{body}");
assert!(body.contains(" type: jsonl\n"), "{body}");
assert!(body.contains("path:"), "expected a path field: {body}");
assert!(
body.contains("# REQUIRED"),
"expected a REQUIRED marker for the required path field: {body}"
);
assert!(
body.contains("source: { ref: default,"),
"expected matrix example referencing the template: {body}"
);
}
#[cfg(all(feature = "source-rest", feature = "sink-jsonl"))]
#[tokio::test]
async fn init_defaults_to_rest_to_jsonl() {
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("pipeline.yaml");
let args = InitArgs {
name: None,
source: None,
sink: None,
output: out.clone(),
force: false,
interactive: false,
template: "default".to_string(),
discover: false,
executable: None,
stream: None,
};
init::run(args).await.expect("init defaults should write");
let body = std::fs::read_to_string(&out).unwrap();
assert!(body.contains("name: my-pipeline\n"), "{body}");
assert!(body.contains(" type: rest\n"), "{body}");
assert!(body.contains(" type: jsonl\n"), "{body}");
}
#[tokio::test]
async fn init_refuses_to_overwrite_existing_file_without_force() {
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("pipeline.yaml");
std::fs::write(&out, "pre-existing").unwrap();
let err = init::run(init_args(out.clone(), None, None))
.await
.expect_err("init must not clobber an existing file");
match err {
CliError::ScaffoldExists { path } => assert_eq!(path, out),
other => panic!("expected ScaffoldExists, got {other:?}"),
}
assert_eq!(std::fs::read_to_string(&out).unwrap(), "pre-existing");
}
#[cfg(all(feature = "source-csv", feature = "sink-jsonl"))]
#[tokio::test]
async fn init_force_overwrites_existing_file() {
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("pipeline.yaml");
std::fs::write(&out, "stale").unwrap();
let mut args = init_args(out.clone(), Some("csv"), Some("jsonl"));
args.force = true;
init::run(args).await.expect("force should overwrite");
let body = std::fs::read_to_string(&out).unwrap();
assert!(body.starts_with("version: 1\n"), "{body}");
assert!(
!body.contains("stale"),
"stale content not replaced: {body}"
);
}
#[tokio::test]
async fn init_rejects_unknown_source_kind() {
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("pipeline.yaml");
let err = init::run(init_args(out, Some("definitely-not-a-source"), None))
.await
.expect_err("unknown source kind must be rejected");
match err {
CliError::UnknownConnector { kind, name, .. } => {
assert_eq!(kind, "source");
assert_eq!(name, "definitely-not-a-source");
}
other => panic!("expected UnknownConnector, got {other:?}"),
}
}
#[cfg(feature = "source-csv")]
#[tokio::test]
async fn init_rejects_unknown_sink_kind() {
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("pipeline.yaml");
let err = init::run(init_args(out, Some("csv"), Some("definitely-not-a-sink")))
.await
.expect_err("unknown sink kind must be rejected");
match err {
CliError::UnknownConnector { kind, name, .. } => {
assert_eq!(kind, "sink");
assert_eq!(name, "definitely-not-a-sink");
}
other => panic!("expected UnknownConnector, got {other:?}"),
}
}
fn preview_args(config: Option<PathBuf>, limit: usize) -> PreviewArgs {
PreviewArgs {
config,
limit,
env_file: None,
no_env_file: true,
profile: None,
}
}
#[cfg(all(feature = "source-csv", feature = "sink-stdout"))]
#[tokio::test]
async fn preview_runs_first_root_csv_source() {
let dir = tempfile::tempdir().unwrap();
let csv = dir.path().join("in.csv");
let cfg = dir.path().join("faucet.yaml");
std::fs::write(&csv, "id,name\n1,alice\n2,bob\n3,carol\n").unwrap();
let yaml = format!(
r#"version: 1
name: preview_smoke
pipeline:
source:
type: csv
config:
path: {csv}
sink:
type: stdout
config: {{}}
"#,
csv = csv.display(),
);
std::fs::write(&cfg, yaml).unwrap();
preview::run(preview_args(Some(cfg), 2))
.await
.expect("preview of a valid csv root should succeed");
}
#[cfg(all(
feature = "source-csv",
feature = "sink-stdout",
feature = "transforms"
))]
#[tokio::test]
async fn preview_applies_transforms_before_emitting() {
let dir = tempfile::tempdir().unwrap();
let csv = dir.path().join("in.csv");
let cfg = dir.path().join("faucet.yaml");
std::fs::write(&csv, "id,name\n1,alice\n").unwrap();
let yaml = format!(
r#"version: 1
name: preview_transform
pipeline:
source:
type: csv
config:
path: {csv}
transforms:
- type: select
config:
fields: [id]
sink:
type: stdout
config: {{}}
"#,
csv = csv.display(),
);
std::fs::write(&cfg, yaml).unwrap();
preview::run(preview_args(Some(cfg), 10))
.await
.expect("preview with a transform should succeed");
}
#[cfg(feature = "sink-stdout")]
#[tokio::test]
async fn preview_unknown_source_kind_errors() {
let dir = tempfile::tempdir().unwrap();
let cfg = dir.path().join("faucet.yaml");
let yaml = r#"version: 1
name: preview_bad
pipeline:
source:
type: definitely-not-a-source
config: {}
sink:
type: stdout
config: {}
"#;
std::fs::write(&cfg, yaml).unwrap();
let err = preview::run(preview_args(Some(cfg), 10))
.await
.expect_err("preview of an unknown source kind must fail");
match err {
CliError::UnknownConnector { kind, name, .. } => {
assert_eq!(kind, "source");
assert_eq!(name, "definitely-not-a-source");
}
other => panic!("expected UnknownConnector, got {other:?}"),
}
}