use tempfile::tempdir;
#[path = "common/mod.rs"]
mod common;
#[allow(dead_code)]
#[path = "fixtures/mod.rs"]
mod fixtures;
use common::cargo_bin;
#[test]
fn cli_no_args_shows_help() {
let output = cargo_bin().output().expect("failed to execute hwp2md");
assert!(
!output.status.success(),
"expected non-zero exit with no args"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("Usage") || stderr.contains("usage"),
"expected 'Usage' in stderr, got: {stderr}"
);
}
#[test]
fn cli_version_flag() {
let output = cargo_bin()
.arg("--version")
.output()
.expect("failed to execute hwp2md --version");
assert!(
output.status.success(),
"expected zero exit for --version; stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains(env!("CARGO_PKG_VERSION")),
"version number not found in stdout: {stdout}"
);
}
#[test]
fn cli_help_flag() {
let output = cargo_bin()
.arg("--help")
.output()
.expect("failed to execute hwp2md --help");
assert!(
output.status.success(),
"expected zero exit for --help; stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("hwp2md"), "binary name missing: {stdout}");
assert!(
stdout.contains("to-md"),
"to-md subcommand missing: {stdout}"
);
assert!(
stdout.contains("to-hwpx"),
"to-hwpx subcommand missing: {stdout}"
);
assert!(stdout.contains("info"), "info subcommand missing: {stdout}");
}
#[test]
fn cli_info_help() {
let output = cargo_bin()
.args(["info", "--help"])
.output()
.expect("failed to execute hwp2md info --help");
assert!(
output.status.success(),
"expected zero exit; stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("input") || stdout.contains("INPUT"),
"input option missing: {stdout}"
);
}
#[test]
fn cli_info_nonexistent_file() {
let output = cargo_bin()
.args(["info", "/nonexistent/path/file.hwpx"])
.output()
.expect("failed to execute hwp2md info");
assert!(
!output.status.success(),
"expected non-zero exit for missing file"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.is_empty(),
"expected some error message on stderr, got empty"
);
}
#[test]
fn cli_check_help() {
let output = cargo_bin()
.args(["check", "--help"])
.output()
.expect("failed to execute hwp2md check --help");
assert!(
output.status.success(),
"expected zero exit; stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("input") || stdout.contains("INPUT"),
"input option missing: {stdout}"
);
}
#[test]
fn cli_check_valid_md_exits_zero() {
let dir = tempdir().expect("tempdir");
let md_file = dir.path().join("valid.md");
std::fs::write(&md_file, b"# Title\n\nSome content.\n").expect("write md");
let output = cargo_bin()
.args(["check", md_file.to_str().unwrap()])
.output()
.expect("failed to execute hwp2md check");
assert!(
output.status.success(),
"expected exit 0 for valid .md; stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("OK"),
"expected 'OK' in stdout, got: {stdout}"
);
}
#[test]
fn cli_check_valid_hwpx_exits_zero() {
let dir = tempdir().expect("tempdir");
let md_src = dir.path().join("src.md");
std::fs::write(&md_src, b"# Check Test\n\nContent.\n").expect("write md");
let hwpx_path = dir.path().join("doc.hwpx");
let conv = cargo_bin()
.args([
"to-hwpx",
md_src.to_str().unwrap(),
"--output",
hwpx_path.to_str().unwrap(),
])
.output()
.expect("failed to run to-hwpx");
assert!(
conv.status.success(),
"to-hwpx failed; stderr: {}",
String::from_utf8_lossy(&conv.stderr)
);
let output = cargo_bin()
.args(["check", hwpx_path.to_str().unwrap()])
.output()
.expect("failed to execute hwp2md check");
assert!(
output.status.success(),
"expected exit 0 for valid .hwpx; stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("OK"),
"expected 'OK' in stdout, got: {stdout}"
);
}
#[test]
fn cli_check_nonexistent_file_exits_nonzero() {
let output = cargo_bin()
.args(["check", "/nonexistent/path/doc.hwpx"])
.output()
.expect("failed to execute hwp2md check");
assert!(
!output.status.success(),
"expected non-zero exit for missing file"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.is_empty(),
"expected error message on stderr, got empty"
);
}
#[test]
fn cli_check_unsupported_extension_exits_nonzero() {
let dir = tempdir().expect("tempdir");
let bad_file = dir.path().join("document.pdf");
std::fs::write(&bad_file, b"fake-pdf").expect("write file");
let output = cargo_bin()
.args(["check", bad_file.to_str().unwrap()])
.output()
.expect("failed to execute hwp2md check");
assert!(
!output.status.success(),
"expected non-zero exit for unsupported extension"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("Unsupported") || stderr.contains("unsupported"),
"expected unsupported-format error, got: {stderr}"
);
}
#[test]
fn cli_check_corrupt_hwpx_exits_nonzero() {
let dir = tempdir().expect("tempdir");
let bad_hwpx = dir.path().join("corrupt.hwpx");
std::fs::write(&bad_hwpx, b"not a zip file at all").expect("write file");
let output = cargo_bin()
.args(["check", bad_hwpx.to_str().unwrap()])
.output()
.expect("failed to execute hwp2md check");
assert!(
!output.status.success(),
"expected non-zero exit for corrupt .hwpx"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.is_empty(),
"expected error message on stderr, got empty"
);
}
#[test]
fn cli_convert_help_lists_supported_pairs() {
let output = cargo_bin()
.args(["convert", "--help"])
.output()
.expect("failed to execute hwp2md convert --help");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains(".hwp") && stdout.contains(".md"),
"convert help must mention supported extensions: {stdout}"
);
}
#[test]
fn cli_convert_md_to_hwpx_creates_output_and_exits_zero() {
let dir = tempdir().expect("tempdir");
let input = dir.path().join("note.md");
std::fs::write(&input, "# Heading\n\nContent.\n").expect("write");
let output = dir.path().join("note.hwpx");
let result = cargo_bin()
.args(["convert", input.to_str().unwrap(), output.to_str().unwrap()])
.output()
.expect("execute convert");
assert!(
result.status.success(),
"convert failed; stderr: {}",
String::from_utf8_lossy(&result.stderr)
);
assert!(output.exists(), "output hwpx not created");
}
#[test]
fn cli_convert_hwpx_to_md_creates_output_and_exits_zero() {
let dir = tempdir().expect("tempdir");
let md_in = dir.path().join("source.md");
std::fs::write(&md_in, "# Hello\n").expect("write");
let hwpx = dir.path().join("source.hwpx");
let _ = cargo_bin()
.args([
"to-hwpx",
md_in.to_str().unwrap(),
"-o",
hwpx.to_str().unwrap(),
])
.output()
.expect("seed hwpx");
assert!(hwpx.exists(), "seed hwpx not produced");
let md_out = dir.path().join("converted.md");
let result = cargo_bin()
.args(["convert", hwpx.to_str().unwrap(), md_out.to_str().unwrap()])
.output()
.expect("execute convert");
assert!(
result.status.success(),
"convert failed; stderr: {}",
String::from_utf8_lossy(&result.stderr)
);
let body = std::fs::read_to_string(&md_out).expect("read md_out");
assert!(body.contains("Hello"), "heading lost: {body:?}");
}
#[test]
fn cli_convert_md_to_md_rejected_with_clear_error() {
let dir = tempdir().expect("tempdir");
let input = dir.path().join("a.md");
let output = dir.path().join("b.md");
std::fs::write(&input, "# x\n").expect("write");
let result = cargo_bin()
.args(["convert", input.to_str().unwrap(), output.to_str().unwrap()])
.output()
.expect("execute convert");
assert!(!result.status.success(), "same-format conversion must fail");
let stderr = String::from_utf8_lossy(&result.stderr);
assert!(
stderr.contains("cannot infer conversion direction"),
"stderr should explain the rejection: {stderr}"
);
}
#[test]
fn convert_refuses_overwrite_without_force() {
let dir = tempdir().expect("tempdir");
let input = dir.path().join("doc.md");
let output = dir.path().join("doc.hwpx");
std::fs::write(&input, "# Test").expect("write input");
std::fs::write(&output, "existing").expect("write pre-existing output");
let result = cargo_bin()
.args(["convert", input.to_str().unwrap(), output.to_str().unwrap()])
.output()
.expect("execute convert");
assert!(
!result.status.success(),
"must fail without --force when output already exists"
);
let stderr = String::from_utf8_lossy(&result.stderr);
assert!(
stderr.contains("already exists"),
"stderr must mention 'already exists': {stderr}"
);
}
#[test]
fn convert_overwrites_with_force_flag() {
let dir = tempdir().expect("tempdir");
let input = dir.path().join("doc.md");
let output = dir.path().join("doc.hwpx");
std::fs::write(&input, "# Test").expect("write input");
std::fs::write(&output, "existing").expect("write pre-existing output");
let result = cargo_bin()
.args([
"convert",
input.to_str().unwrap(),
output.to_str().unwrap(),
"--force",
])
.output()
.expect("execute convert");
assert!(
result.status.success(),
"must succeed with --force: {}",
String::from_utf8_lossy(&result.stderr)
);
let content = std::fs::read(&output).expect("read output");
assert_ne!(
content, b"existing",
"output must be overwritten by --force conversion"
);
}
#[test]
fn convert_frontmatter_flag_adds_yaml_header() {
let dir = tempdir().expect("tempdir");
let hwpx = dir.path().join("doc.hwpx");
common::make_hwpx(&hwpx);
let md_out = dir.path().join("doc.md");
let result = cargo_bin()
.args([
"convert",
hwpx.to_str().unwrap(),
md_out.to_str().unwrap(),
"--frontmatter",
])
.output()
.expect("execute convert --frontmatter");
assert!(
result.status.success(),
"convert --frontmatter failed: {}",
String::from_utf8_lossy(&result.stderr)
);
let body = std::fs::read_to_string(&md_out).expect("read md");
assert!(
body.starts_with("---"),
"expected YAML frontmatter: {body:?}"
);
}
#[test]
fn convert_style_flag_accepted_for_md_to_hwpx() {
let dir = tempdir().expect("tempdir");
let input = dir.path().join("styled.md");
std::fs::write(&input, "# Styled\n\nBody.\n").expect("write md");
let style_yml = dir.path().join("style.yml");
std::fs::write(&style_yml, "page:\n width: 210\n height: 297\n").expect("write style");
let output = dir.path().join("styled.hwpx");
let result = cargo_bin()
.args([
"convert",
input.to_str().unwrap(),
output.to_str().unwrap(),
"--style",
style_yml.to_str().unwrap(),
])
.output()
.expect("execute convert --style");
assert!(
result.status.success(),
"convert --style failed: {}",
String::from_utf8_lossy(&result.stderr)
);
assert!(output.exists(), "styled hwpx not created");
}
#[test]
fn convert_assets_dir_flag_accepted() {
let dir = tempdir().expect("tempdir");
let hwpx = dir.path().join("doc.hwpx");
common::make_hwpx(&hwpx);
let md_out = dir.path().join("doc.md");
let assets = dir.path().join("assets");
let result = cargo_bin()
.args([
"convert",
hwpx.to_str().unwrap(),
md_out.to_str().unwrap(),
"--assets-dir",
assets.to_str().unwrap(),
])
.output()
.expect("execute convert --assets-dir");
assert!(
result.status.success(),
"convert --assets-dir failed: {}",
String::from_utf8_lossy(&result.stderr)
);
assert!(md_out.exists(), "md output not created");
}
#[test]
fn convert_assets_dir_extracts_embedded_image() {
let png_data = vec![0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
let fixture = fixtures::HwpxFixture::new()
.section(&fixtures::para_xml("Image doc"))
.bin_data("logo.png", png_data.clone());
let (dir, hwpx_path) = fixture.write_to_tempfile();
let md_out = dir.path().join("out.md");
let assets = dir.path().join("extracted");
let result = cargo_bin()
.args([
"convert",
hwpx_path.to_str().unwrap(),
md_out.to_str().unwrap(),
"--assets-dir",
assets.to_str().unwrap(),
])
.output()
.expect("execute convert --assets-dir");
assert!(
result.status.success(),
"convert --assets-dir failed: {}",
String::from_utf8_lossy(&result.stderr)
);
let extracted = assets.join("logo.png");
assert!(extracted.exists(), "image must be extracted to assets dir");
assert_eq!(
std::fs::read(&extracted).unwrap(),
png_data,
"extracted image data must match original"
);
}