use backhopper_core::model::names::{
ApplicationName, FieldName, ModuleName, RecordName, RelativePath,
};
use backhopper_core::model::snapshot::{
FORMAT_VERSION, LAST_MIGRATABLE_FORMAT_VERSION, Module, RecordDecl, RecordField,
SUPPORTED_FORMAT_VERSIONS,
};
use backhopper_core::snapshot::{format, parser};
use backhopper_test_support::{canonical_snapshot, snapshot_header};
#[test]
fn format_version_constant_is_four() {
assert_eq!(FORMAT_VERSION, 5);
}
#[test]
fn supported_versions_include_v1_through_v4() {
assert!(SUPPORTED_FORMAT_VERSIONS.contains(&1));
assert!(SUPPORTED_FORMAT_VERSIONS.contains(&2));
assert!(SUPPORTED_FORMAT_VERSIONS.contains(&3));
assert!(SUPPORTED_FORMAT_VERSIONS.contains(&4));
assert!(SUPPORTED_FORMAT_VERSIONS.contains(&5));
}
fn v1_text(body: &str) -> String {
format!(
"# backhopper snapshot\n# format-version: 1\n# project: p\n# tag: v1.0.0\n# commit: 0000000000000000000000000000000000000000\n# scanned-paths: src\n# generated-by: test\n# generated-at: 1970-01-01T00:00:00Z\n\n{body}",
)
}
fn v2_text(body: &str) -> String {
format!(
"# backhopper snapshot\n# format-version: {FORMAT_VERSION}\n# project: p\n# tag: v1.0.0\n# commit: 0000000000000000000000000000000000000000\n# scanned-paths: src\n# generated-by: test\n# generated-at: 1970-01-01T00:00:00Z\n\n{body}",
)
}
#[test]
fn parser_accepts_v1_format() {
let text = v1_text("module alpha\n export hello/0\n");
let parsed = parser::parse(&text).expect("v1 snapshot parses");
assert_eq!(parsed.modules().len(), 1);
assert_eq!(parsed.modules()[0].name.as_str(), "alpha");
}
#[test]
fn parser_accepts_v2_format() {
let text = v2_text("module alpha\n path src/alpha.erl\n app rabbit\n export hello/0\n");
let parsed = parser::parse(&text).expect("v2 snapshot parses");
let alpha = parsed
.module_named(&ModuleName::new("alpha").unwrap())
.unwrap();
assert_eq!(
alpha.path.as_ref().map(RelativePath::as_str),
Some("src/alpha.erl")
);
assert_eq!(alpha.app.as_ref().map(|a| a.as_str()), Some("rabbit"));
}
#[test]
fn parser_rejects_unknown_format_version() {
let text = "# backhopper snapshot\n# format-version: 99\n# project: p\n# tag: v1.0.0\n# commit: 0000000000000000000000000000000000000000\n# scanned-paths: src\n# generated-by: test\n# generated-at: 1970-01-01T00:00:00Z\n";
assert!(parser::parse(text).is_err());
}
#[test]
fn writer_emits_the_snapshots_own_format_header() {
let snap = canonical_snapshot(
snapshot_header("p", "v1"),
vec![Module::new(ModuleName::new("alpha").unwrap())],
);
let text = format::to_string(&snap).expect("write");
let expected = format!("# format-version: {LAST_MIGRATABLE_FORMAT_VERSION}");
assert!(text.contains(&expected), "text was:\n{text}");
}
#[test]
fn module_path_and_app_round_trip_through_format() {
let mut m = Module::new(ModuleName::new("alpha").unwrap());
m.path = Some(RelativePath::new("deps/rabbit/src/alpha.erl").unwrap());
m.app = Some(ApplicationName::new("rabbit").unwrap());
let snap = canonical_snapshot(snapshot_header("p", "v1"), vec![m]);
let text = format::to_string(&snap).expect("write");
let reparsed = parser::parse(&text).expect("re-parse");
let alpha = reparsed
.module_named(&ModuleName::new("alpha").unwrap())
.unwrap();
assert_eq!(
alpha.path.as_ref().map(RelativePath::as_str),
Some("deps/rabbit/src/alpha.erl")
);
assert_eq!(alpha.app.as_ref().map(|a| a.as_str()), Some("rabbit"));
}
#[test]
fn module_records_round_trip_through_format() {
let mut m = Module::new(ModuleName::new("alpha").unwrap());
m.records.push(RecordDecl {
name: RecordName::new("state").unwrap(),
fields: vec![
RecordField {
name: FieldName::new("a").unwrap(),
type_repr: None,
default: None,
},
RecordField {
name: FieldName::new("b").unwrap(),
type_repr: Some("integer()".into()),
default: None,
},
],
});
let snap = canonical_snapshot(snapshot_header("p", "v1"), vec![m]);
let text = format::to_string(&snap).expect("write");
let reparsed = parser::parse(&text).expect("re-parse");
let alpha = reparsed
.module_named(&ModuleName::new("alpha").unwrap())
.unwrap();
assert_eq!(alpha.records.len(), 1);
assert_eq!(alpha.records[0].name.as_str(), "state");
assert_eq!(alpha.records[0].fields.len(), 2);
assert_eq!(
alpha.records[0].fields[1].type_repr.as_deref(),
Some("integer()")
);
}