use serde_json::Value;
use std::fs;
use std::process::Command;
use tempfile::TempDir;
#[test]
fn test_marmite_binary_help() {
let output = Command::new("cargo")
.args(["run", "--quiet", "--", "--help"])
.output()
.expect("Failed to execute marmite");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("marmite"));
assert!(stdout.contains("Usage:"));
}
#[test]
fn test_marmite_version() {
let output = Command::new("cargo")
.args(["run", "--quiet", "--", "--version"])
.output()
.expect("Failed to execute marmite");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("marmite"));
}
#[test]
fn test_minimal_site_generation() {
let temp_dir = TempDir::new().unwrap();
let input_dir = temp_dir.path().join("input");
let output_dir = temp_dir.path().join("output");
fs::create_dir_all(&input_dir).unwrap();
fs::create_dir_all(input_dir.join("content")).unwrap();
let config_path = input_dir.join("marmite.yaml");
fs::write(&config_path, "name: Test Site\ntagline: Test").unwrap();
let content_path = input_dir.join("content").join("test.md");
fs::write(&content_path, "# Test Page\n\nThis is a test.").unwrap();
let output = Command::new("cargo")
.args([
"run",
"--quiet",
"--",
input_dir.to_str().unwrap(),
output_dir.to_str().unwrap(),
])
.output()
.expect("Failed to execute marmite");
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
assert!(output_dir.join("index.html").exists());
assert!(output_dir.join("test.html").exists());
}
#[test]
fn test_site_initialization() {
let temp_dir = TempDir::new().unwrap();
let site_dir = temp_dir.path().join("new_site");
let output = Command::new("cargo")
.args([
"run",
"--quiet",
"--",
site_dir.to_str().unwrap(),
"--init-site",
])
.output()
.expect("Failed to execute marmite");
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
assert!(site_dir.exists());
assert!(site_dir.join("marmite.yaml").exists());
assert!(site_dir.join("content").exists());
assert!(site_dir.join("content").join("media").exists());
assert!(
site_dir.join("content").join("pages").exists(),
"content/pages/ should be created"
);
assert!(
site_dir.join("content").join("posts").exists(),
"content/posts/ should be created"
);
assert!(
site_dir
.join("content")
.join("pages")
.join("about.md")
.exists(),
"about.md should be in content/pages/"
);
assert!(
site_dir
.join("content")
.join("posts")
.join("welcome.md")
.exists(),
"welcome.md should be in content/posts/"
);
assert!(site_dir.join("custom.css").exists());
assert!(site_dir.join("custom.js").exists());
}
#[test]
fn test_new_with_directory_flag() {
let temp_dir = TempDir::new().unwrap();
let site_dir = temp_dir.path().join("dir_site");
Command::new("cargo")
.args([
"run",
"--quiet",
"--",
site_dir.to_str().unwrap(),
"--init-site",
])
.output()
.expect("Failed to init site");
let output = Command::new("cargo")
.args([
"run",
"--quiet",
"--",
site_dir.to_str().unwrap(),
"--new",
"Test Post",
"-d",
"posts",
])
.output()
.expect("Failed to create post");
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let json: Value = serde_json::from_str(stdout.trim()).expect("Output should be valid JSON");
let file_path = json["file"].as_str().unwrap();
assert!(
file_path.contains("posts"),
"Output file path should contain 'posts' directory: {file_path}"
);
assert_eq!(json["title"].as_str().unwrap(), "Test Post");
assert_eq!(json["slug"].as_str().unwrap(), "test-post");
assert!(
json["date"].as_str().is_some(),
"Posts should have a date field"
);
let output = Command::new("cargo")
.args([
"run",
"--quiet",
"--",
site_dir.to_str().unwrap(),
"--new",
"Contact",
"-p",
"-d",
"pages",
])
.output()
.expect("Failed to create page");
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
assert!(
site_dir
.join("content")
.join("pages")
.join("contact.md")
.exists(),
"Page should be created in content/pages/"
);
let output = Command::new("cargo")
.args([
"run",
"--quiet",
"--",
site_dir.to_str().unwrap(),
"--new",
"Tutorial One",
"-d",
"tutorials",
])
.output()
.expect("Failed to create in new dir");
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
assert!(
site_dir.join("content").join("tutorials").exists(),
"Directory should be auto-created"
);
let stdout = String::from_utf8_lossy(&output.stdout);
let json: Value = serde_json::from_str(stdout.trim()).expect("Output should be valid JSON");
let file_path = json["file"].as_str().unwrap();
assert!(
file_path.contains("tutorials"),
"Output file path should contain 'tutorials' directory: {file_path}"
);
}
#[test]
fn test_new_json_output_format() {
let temp_dir = TempDir::new().unwrap();
let site_dir = temp_dir.path().join("json_site");
Command::new("cargo")
.args([
"run",
"--quiet",
"--",
site_dir.to_str().unwrap(),
"--init-site",
])
.output()
.expect("Failed to init site");
let output = Command::new("cargo")
.args([
"run",
"--quiet",
"--",
site_dir.to_str().unwrap(),
"--new",
"My Post",
"-t",
"rust,web",
])
.output()
.expect("Failed to create post");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let json: Value = serde_json::from_str(stdout.trim()).expect("Output should be valid JSON");
assert_eq!(json["title"].as_str().unwrap(), "My Post");
assert_eq!(json["slug"].as_str().unwrap(), "my-post");
assert!(json["file"].as_str().unwrap().ends_with(".md"));
assert!(json["date"].as_str().is_some());
assert_eq!(json["tags"].as_str().unwrap(), "rust,web");
let output = Command::new("cargo")
.args([
"run",
"--quiet",
"--",
site_dir.to_str().unwrap(),
"--new",
"About",
"-p",
])
.output()
.expect("Failed to create page");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let json: Value = serde_json::from_str(stdout.trim()).expect("Output should be valid JSON");
assert_eq!(json["title"].as_str().unwrap(), "About");
assert!(json["date"].is_null(), "Pages should not have a date field");
}
#[test]
fn test_new_with_lang_standalone() {
let temp_dir = TempDir::new().unwrap();
let site_dir = temp_dir.path().join("lang_site");
Command::new("cargo")
.args([
"run",
"--quiet",
"--",
site_dir.to_str().unwrap(),
"--init-site",
])
.output()
.expect("Failed to init site");
let output = Command::new("cargo")
.args([
"run",
"--quiet",
"--",
site_dir.to_str().unwrap(),
"--new",
"Ola Mundo",
"--lang",
"pt",
])
.output()
.expect("Failed to create post with lang");
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let json: Value = serde_json::from_str(stdout.trim()).expect("Output should be valid JSON");
assert_eq!(json["language"].as_str().unwrap(), "pt");
let file_path = json["file"].as_str().unwrap();
let content = fs::read_to_string(file_path).unwrap();
assert!(
content.contains("language: pt"),
"Frontmatter should contain language field"
);
}
#[test]
fn test_new_with_invalid_lang() {
let temp_dir = TempDir::new().unwrap();
let site_dir = temp_dir.path().join("invalid_lang_site");
Command::new("cargo")
.args([
"run",
"--quiet",
"--",
site_dir.to_str().unwrap(),
"--init-site",
])
.output()
.expect("Failed to init site");
let output = Command::new("cargo")
.args([
"run",
"--quiet",
"--",
site_dir.to_str().unwrap(),
"--new",
"Test",
"--lang",
"xyz",
])
.output()
.expect("Failed to run command");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("Invalid language code"),
"Should report invalid language code: {stderr}"
);
}
#[test]
fn test_new_translation_subfolder() {
let temp_dir = TempDir::new().unwrap();
let site_dir = temp_dir.path().join("trans_sub_site");
Command::new("cargo")
.args([
"run",
"--quiet",
"--",
site_dir.to_str().unwrap(),
"--init-site",
])
.output()
.expect("Failed to init site");
let output = Command::new("cargo")
.args([
"run",
"--quiet",
"--",
site_dir.to_str().unwrap(),
"--new",
"Hello World",
"-p",
"-d",
"hello-world",
])
.output()
.expect("Failed to create original post");
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let output = Command::new("cargo")
.args([
"run",
"--quiet",
"--",
site_dir.to_str().unwrap(),
"--new",
"Ola Mundo",
"--lang",
"pt",
"--translates",
"hello-world",
])
.output()
.expect("Failed to create translation");
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let json: Value = serde_json::from_str(stdout.trim()).expect("Output should be valid JSON");
assert_eq!(json["language"].as_str().unwrap(), "pt");
assert_eq!(json["translates"].as_str().unwrap(), "hello-world");
let file_path = json["file"].as_str().unwrap();
assert!(
file_path.contains("hello-world/pt-ola-mundo.md"),
"Translation should be in the original's subfolder with lang prefix: {file_path}"
);
assert!(
std::path::Path::new(file_path).exists(),
"Translation file should exist"
);
let content = fs::read_to_string(file_path).unwrap();
assert!(content.contains("language: pt"));
assert!(
!content.contains("translates:"),
"Subfolder translations should not have translates frontmatter"
);
}
#[test]
fn test_new_translation_root_level() {
let temp_dir = TempDir::new().unwrap();
let site_dir = temp_dir.path().join("trans_root_site");
Command::new("cargo")
.args([
"run",
"--quiet",
"--",
site_dir.to_str().unwrap(),
"--init-site",
])
.output()
.expect("Failed to init site");
Command::new("cargo")
.args([
"run",
"--quiet",
"--",
site_dir.to_str().unwrap(),
"--new",
"About Page",
"-p",
])
.output()
.expect("Failed to create original page");
let output = Command::new("cargo")
.args([
"run",
"--quiet",
"--",
site_dir.to_str().unwrap(),
"--new",
"Pagina Sobre",
"-p",
"--lang",
"pt",
"--translates",
"about-page",
])
.output()
.expect("Failed to create translation");
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let json: Value = serde_json::from_str(stdout.trim()).expect("Output should be valid JSON");
let file_path = json["file"].as_str().unwrap();
let content = fs::read_to_string(file_path).unwrap();
assert!(content.contains("language: pt"));
assert!(
content.contains("translates: about-page"),
"Root-level translations should have translates frontmatter: {content}"
);
}
#[test]
fn test_new_translation_nonexistent_slug() {
let temp_dir = TempDir::new().unwrap();
let site_dir = temp_dir.path().join("trans_noexist_site");
Command::new("cargo")
.args([
"run",
"--quiet",
"--",
site_dir.to_str().unwrap(),
"--init-site",
])
.output()
.expect("Failed to init site");
let output = Command::new("cargo")
.args([
"run",
"--quiet",
"--",
site_dir.to_str().unwrap(),
"--new",
"Translation",
"--lang",
"pt",
"--translates",
"nonexistent-slug",
])
.output()
.expect("Failed to run command");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("Cannot find content with slug"),
"Should report missing original content: {stderr}"
);
}
#[test]
fn test_show_urls_command() {
let temp_dir = TempDir::new().unwrap();
let input_dir = temp_dir.path().join("input");
fs::create_dir_all(&input_dir).unwrap();
fs::create_dir_all(input_dir.join("content")).unwrap();
let config_path = input_dir.join("marmite.yaml");
fs::write(&config_path, "name: Test\nurl: https://example.com").unwrap();
fs::write(input_dir.join("content").join("page1.md"), "# Page 1").unwrap();
fs::write(input_dir.join("content").join("page2.md"), "# Page 2").unwrap();
let output = Command::new("cargo")
.args([
"run",
"--quiet",
"--",
input_dir.to_str().unwrap(),
"--show-urls",
])
.output()
.expect("Failed to execute marmite");
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("https://example.com/page1.html"));
assert!(stdout.contains("https://example.com/page2.html"));
assert!(stdout.contains("https://example.com/index.html"));
}
#[test]
fn test_standard_site_atproto_generation() {
let temp_dir = TempDir::new().unwrap();
let input_dir = temp_dir.path().join("input");
let output_dir = temp_dir.path().join("output");
fs::create_dir_all(&input_dir).unwrap();
fs::create_dir_all(input_dir.join("content")).unwrap();
let config_path = input_dir.join("marmite.yaml");
fs::write(
&config_path,
r#"name: Test ATProto Blog
url: https://myblog.com
atproto:
handle: test.bsky.social
publication_uri: at://did:plc:123/site.standard.publication/456
"#,
)
.unwrap();
let content_path = input_dir.join("content").join("first-post.md");
fs::write(&content_path, "# First Post\n\nHello standard.site!").unwrap();
let state_path = input_dir.join(".marmite-atproto-state.json");
fs::write(
&state_path,
r#"{"posts":{"first-post":{"content_hash":"abc","at_uri":"at://did:plc:123/site.standard.document/first-post","last_published":"2026-06-17T17:08:42Z"}}}"#
).unwrap();
let output = Command::new("cargo")
.args([
"run",
"--quiet",
"--",
input_dir.to_str().unwrap(),
output_dir.to_str().unwrap(),
])
.output()
.expect("Failed to execute marmite");
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let wk_file = output_dir
.join(".well-known")
.join("site.standard.publication");
assert!(wk_file.exists());
let wk_content = fs::read_to_string(&wk_file).unwrap();
assert_eq!(
wk_content.trim(),
"at://did:plc:123/site.standard.publication/456"
);
let index_file = output_dir.join("index.html");
assert!(index_file.exists());
let index_content = fs::read_to_string(&index_file).unwrap();
assert!(index_content.contains(r#"<link rel="site.standard.publication" href="at://did:plc:123/site.standard.publication/456">"#));
let post_file = output_dir.join("first-post.html");
assert!(post_file.exists());
let post_content = fs::read_to_string(&post_file).unwrap();
assert!(post_content.contains(r#"<link rel="site.standard.document" href="at://did:plc:123/site.standard.document/first-post">"#));
}