use etdl_compiler::Compiler;
use etdl_parser::{load_asyncapi_imports, parse_document_from_file};
use etdl_target_java::JavaCodeGenerator;
use std::path::{Path, PathBuf};
use std::process::Command;
fn fixtures_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
}
fn generate_java(fixture: &str, stem: &str) -> etdl_compiler::TargetCompilationResult {
let base = fixtures_dir();
let doc = parse_document_from_file(&base.join(fixture)).expect("parse fixture");
let registry = load_asyncapi_imports(&doc, &base).expect("load asyncapi imports");
let compiler = Compiler::new();
let generator = JavaCodeGenerator::new();
compiler.compile_target_with_base(&doc, ®istry, &base, &generator, stem)
}
#[test]
fn java_generation_order_fulfillment_produces_expected_files() {
let result = generate_java("order-fulfillment.etdl", "order-fulfillment");
assert!(
result.diagnostics.iter().all(|d| !d.is_error()),
"unexpected diagnostics: {:?}",
result.diagnostics
);
let files = result.files.expect("generation produced files");
let paths: Vec<&str> = files.iter().map(|f| f.relative_path.as_str()).collect();
assert!(paths.contains(&"etdl/runtime/WorkflowError.java"));
assert!(paths.contains(&"etdl/runtime/Publisher.java"));
assert!(paths.contains(&"etdl/runtime/RetryPolicy.java"));
assert!(paths.contains(&"fulfillmentcontext/OrderPlaced.java"));
assert!(paths.contains(&"fulfillmentcontext/OrderFulfillmentHandlers.java"));
assert!(paths.contains(&"fulfillmentcontext/OrderFulfillmentWorkflow.java"));
let workflow = files
.iter()
.find(|f| f.relative_path == "fulfillmentcontext/OrderFulfillmentWorkflow.java")
.unwrap();
assert!(workflow.contents.contains("public final class OrderFulfillmentWorkflow"));
assert!(workflow.contents.contains("RetryPolicy retry = new RetryPolicy(3, 250, BackoffStrategy.EXPONENTIAL)"));
assert!(workflow.contents.contains("PROCESS_PAYMENT_OPERATION_FAILURE_PROBABILITY"));
let handlers = files
.iter()
.find(|f| f.relative_path == "fulfillmentcontext/OrderFulfillmentHandlers.java")
.unwrap();
assert!(handlers.contents.contains("public interface OrderFulfillmentHandlers"));
assert!(handlers.contents.contains("Object stripeChargeHandler(OrderPlaced message) throws WorkflowError;"));
}
#[test]
fn java_generation_inline_messages_resolves_internal_refs() {
let result = generate_java("inline-messages.etdl", "inline-messages");
assert!(result.diagnostics.iter().all(|d| !d.is_error()));
let files = result.files.expect("generation produced files");
let order_placed = files
.iter()
.find(|f| f.relative_path == "fulfillmentcontext/OrderPlaced.java")
.expect("Internal Message Reference resolved to a generated record");
assert!(order_placed.contents.contains("public record OrderPlaced"));
assert!(order_placed.contents.contains("String orderId"));
}
#[test]
fn document_domain_lowercases_into_the_java_package() {
let doc_yaml = r##"
etdl: "1.0.0"
info:
title: "T"
version: "1.0.0"
domain: "AcmeOrders123"
eventTrees:
T:
initiatingEvent:
id: I
message: "#/components/messages/M"
next: C
nodes:
C:
type: consequence
operation: terminate
components:
messages:
M:
payload:
type: object
"##;
let doc: etdl_parser::ast::EtlDocument = serde_yaml::from_str(doc_yaml).expect("valid yaml");
let registry = etdl_parser::asyncapi::AsyncApiRegistry::new();
let compiler = Compiler::new();
let generator = JavaCodeGenerator::new();
let result = compiler.compile_target(&doc, ®istry, &generator, "t");
assert!(result.diagnostics.iter().all(|d| !d.is_error()));
let files = result.files.expect("generation produced files");
assert!(
files.iter().any(|f| f.relative_path.starts_with("acmeorders123/")),
"expected the lowercased domain as package dir, got: {:?}",
files.iter().map(|f| &f.relative_path).collect::<Vec<_>>()
);
}
#[test]
fn generator_package_override_takes_precedence_over_domain() {
let base = fixtures_dir();
let doc = parse_document_from_file(&base.join("inline-messages.etdl")).expect("parse");
let registry = load_asyncapi_imports(&doc, &base).expect("asyncapi");
let compiler = Compiler::new();
let generator = JavaCodeGenerator {
version: "test".to_string(),
package: Some("com.acme.custom".to_string()),
};
let result = compiler.compile_target_with_base(&doc, ®istry, &base, &generator, "inline-messages");
let files = result.files.expect("generation succeeded");
assert!(
files.iter().any(|f| f.relative_path.starts_with("com/acme/custom/")),
"got: {:?}",
files.iter().map(|f| &f.relative_path).collect::<Vec<_>>()
);
}
#[test]
fn semantic_equivalence_same_probability_reaches_both_targets() {
let base = fixtures_dir();
let doc = parse_document_from_file(&base.join("order-fulfillment.etdl")).expect("parse");
let registry = load_asyncapi_imports(&doc, &base).expect("asyncapi");
let compiler = Compiler::new();
let rust_result = compiler.compile_with_base(&doc, ®istry, &base);
let rust_output = rust_result.rust_output.expect("rust generation succeeded");
let java_generator = JavaCodeGenerator::new();
let java_result =
compiler.compile_target_with_base(&doc, ®istry, &base, &java_generator, "order-fulfillment");
let java_files = java_result.files.expect("java generation succeeded");
let java_workflow = java_files
.iter()
.find(|f| f.relative_path.ends_with("Workflow.java"))
.unwrap();
assert!(rust_output.contains("0.012987"), "rust output: {rust_output}");
assert!(java_workflow.contents.contains("0.012987"), "java output: {}", java_workflow.contents);
}
fn javac_available() -> bool {
Command::new("javac")
.arg("-version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
fn compile_with_javac(files: &[etdl_compiler::GeneratedFile], src_dir: &Path, out_dir: &Path) {
for f in files {
let path = src_dir.join(&f.relative_path);
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(&path, &f.contents).unwrap();
}
std::fs::create_dir_all(out_dir).unwrap();
let java_files: Vec<PathBuf> = files.iter().map(|f| src_dir.join(&f.relative_path)).collect();
let output = Command::new("javac")
.arg("--release")
.arg("21")
.arg("--enable-preview")
.arg("-d")
.arg(out_dir)
.args(&java_files)
.output()
.expect("failed to invoke javac");
assert!(
output.status.success(),
"javac failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn javac_compiles_order_fulfillment_output() {
if !javac_available() {
eprintln!("skipping: javac not found on PATH");
return;
}
let result = generate_java("order-fulfillment.etdl", "order-fulfillment");
let files = result.files.expect("generation succeeded");
let tmp = std::env::temp_dir().join(format!(
"etdl-java-gencheck-of-{}",
std::process::id()
));
let _ = std::fs::remove_dir_all(&tmp);
compile_with_javac(&files, &tmp.join("src"), &tmp.join("out"));
let _ = std::fs::remove_dir_all(&tmp);
}
#[test]
fn javac_compiles_inline_messages_output() {
if !javac_available() {
eprintln!("skipping: javac not found on PATH");
return;
}
let result = generate_java("inline-messages.etdl", "inline-messages");
let files = result.files.expect("generation succeeded");
let tmp = std::env::temp_dir().join(format!(
"etdl-java-gencheck-im-{}",
std::process::id()
));
let _ = std::fs::remove_dir_all(&tmp);
compile_with_javac(&files, &tmp.join("src"), &tmp.join("out"));
let _ = std::fs::remove_dir_all(&tmp);
}