Skip to content

Commit a0a0eb9

Browse files
committed
Add testcase
1 parent e8600fd commit a0a0eb9

1 file changed

Lines changed: 241 additions & 8 deletions

File tree

  • crates/vespertide-exporter/src/seaorm

crates/vespertide-exporter/src/seaorm/mod.rs

Lines changed: 241 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -887,10 +887,6 @@ fn render_self_ref_link_helpers(
887887
crate_prefix,
888888
);
889889

890-
if self_ref_junction.role_columns.len() < 2 {
891-
continue;
892-
}
893-
894890
for (from_idx, from_role) in self_ref_junction.role_relations.iter().enumerate() {
895891
for (to_idx, to_role) in self_ref_junction.role_relations.iter().enumerate() {
896892
if from_idx == to_idx {
@@ -943,10 +939,6 @@ fn render_self_ref_query_helpers(table: &TableDef, schema: &[TableDef]) -> Vec<S
943939
continue;
944940
};
945941

946-
if self_ref_junction.role_columns.len() < 2 {
947-
continue;
948-
}
949-
950942
for (from_idx, from_col) in self_ref_junction.role_columns.iter().enumerate() {
951943
for (to_idx, to_col) in self_ref_junction.role_columns.iter().enumerate() {
952944
if from_idx == to_idx {
@@ -1790,6 +1782,77 @@ mod module_path_tests {
17901782
assert_eq!(result, "crate::models::estimate::estimate");
17911783
}
17921784

1785+
#[test]
1786+
fn resolve_relation_entity_module_path_uses_super_for_same_directory() {
1787+
let mut module_paths = HashMap::new();
1788+
module_paths.insert("admin".into(), vec!["shared".into(), "admin".into()]);
1789+
module_paths.insert(
1790+
"admin_stamp".into(),
1791+
vec!["shared".into(), "admin_stamp".into()],
1792+
);
1793+
let result = resolve_relation_entity_module_path(
1794+
"admin",
1795+
"admin_stamp",
1796+
&module_paths,
1797+
"crate::models",
1798+
);
1799+
assert_eq!(result, "super::admin_stamp");
1800+
}
1801+
1802+
#[test]
1803+
fn resolve_relation_entity_module_path_fallback_super_when_empty_prefix_cross_directory() {
1804+
let mut module_paths = HashMap::new();
1805+
module_paths.insert("admin".into(), vec!["admin".into(), "admin".into()]);
1806+
module_paths.insert(
1807+
"estimate".into(),
1808+
vec!["estimate".into(), "estimate".into()],
1809+
);
1810+
let result = resolve_relation_entity_module_path("admin", "estimate", &module_paths, "");
1811+
assert_eq!(result, "super::estimate");
1812+
}
1813+
1814+
#[test]
1815+
fn resolve_relation_entity_module_path_uses_crate_prefix_when_not_in_module_paths() {
1816+
let module_paths = HashMap::new();
1817+
let result = resolve_relation_entity_module_path(
1818+
"admin",
1819+
"estimate",
1820+
&module_paths,
1821+
"crate::models",
1822+
);
1823+
assert_eq!(result, "crate::models::estimate");
1824+
}
1825+
1826+
#[test]
1827+
fn resolve_self_ref_link_module_path_uses_super_for_same_directory() {
1828+
let mut module_paths = HashMap::new();
1829+
module_paths.insert("admin".into(), vec!["shared".into(), "admin".into()]);
1830+
module_paths.insert(
1831+
"admin_friendship".into(),
1832+
vec!["shared".into(), "admin_friendship".into()],
1833+
);
1834+
let result = resolve_self_ref_link_module_path(
1835+
"admin",
1836+
"admin_friendship",
1837+
&module_paths,
1838+
"crate::models",
1839+
);
1840+
assert_eq!(result, "super::admin_friendship");
1841+
}
1842+
1843+
#[test]
1844+
fn resolve_self_ref_link_module_path_absolute_fallback_when_empty_prefix() {
1845+
let mut module_paths = HashMap::new();
1846+
module_paths.insert("admin".into(), vec!["admin".into(), "admin".into()]);
1847+
module_paths.insert(
1848+
"admin_friendship".into(),
1849+
vec!["social".into(), "admin_friendship".into()],
1850+
);
1851+
let result =
1852+
resolve_self_ref_link_module_path("admin", "admin_friendship", &module_paths, "");
1853+
assert_eq!(result, "crate::models::social::admin_friendship");
1854+
}
1855+
17931856
#[test]
17941857
fn self_ref_link_helpers_use_crate_path_for_cross_directory_junctions() {
17951858
let admin = TableDef {
@@ -1974,6 +2037,40 @@ mod helper_tests {
19742037
assert_eq!(unique_name("other", &mut used), "other_1");
19752038
}
19762039

2040+
#[test]
2041+
fn test_unique_relation_enum_name_preferred_available() {
2042+
let used = HashSet::new();
2043+
let result = unique_relation_enum_name("User".into(), "post", "User", &used);
2044+
assert_eq!(result, "User");
2045+
}
2046+
2047+
#[test]
2048+
fn test_unique_relation_enum_name_source_prefixed() {
2049+
let mut used = HashSet::new();
2050+
used.insert("User".into());
2051+
let result = unique_relation_enum_name("User".into(), "post", "User", &used);
2052+
assert_eq!(result, "PostUser");
2053+
}
2054+
2055+
#[test]
2056+
fn test_unique_relation_enum_name_numbered_fallback() {
2057+
let mut used = HashSet::new();
2058+
used.insert("User".into());
2059+
used.insert("PostUser".into());
2060+
let result = unique_relation_enum_name("User".into(), "post", "User", &used);
2061+
assert_eq!(result, "PostUser2");
2062+
}
2063+
2064+
#[test]
2065+
fn test_unique_relation_enum_name_numbered_fallback_skips_taken() {
2066+
let mut used = HashSet::new();
2067+
used.insert("User".into());
2068+
used.insert("PostUser".into());
2069+
used.insert("PostUser2".into());
2070+
let result = unique_relation_enum_name("User".into(), "post", "User", &used);
2071+
assert_eq!(result, "PostUser3");
2072+
}
2073+
19772074
#[rstest]
19782075
#[case(vec!["creator_user_id".into()], "CreatorUser")]
19792076
#[case(vec!["used_by_user_id".into()], "UsedByUser")]
@@ -4124,6 +4221,142 @@ mod tests {
41244221
assert!(result.contains("#[sea_orm(table_name = \"users\")]"));
41254222
}
41264223

4224+
#[test]
4225+
fn test_junction_relation_enum_without_via_when_entity_appears_multiple_times() {
4226+
use vespertide_core::schema::primary_key::PrimaryKeySyntax;
4227+
4228+
// user has a forward FK to user_tag (composite FK), making user_tag appear
4229+
// in both forward and reverse targets => entity_count > 1 for user_tag.
4230+
// The junction table entry from collect_many_to_many_relations has via=None, via_rel=None,
4231+
// so when needs_relation_enum is true, it hits the branch with only relation_enum (no via/via_rel).
4232+
let user = TableDef {
4233+
name: "user".into(),
4234+
description: None,
4235+
columns: vec![
4236+
ColumnDef {
4237+
name: "id".into(),
4238+
r#type: ColumnType::Simple(SimpleColumnType::Integer),
4239+
nullable: false,
4240+
default: None,
4241+
comment: None,
4242+
primary_key: Some(PrimaryKeySyntax::Bool(true)),
4243+
unique: None,
4244+
index: None,
4245+
foreign_key: None,
4246+
},
4247+
ColumnDef {
4248+
name: "pinned_user_id".into(),
4249+
r#type: ColumnType::Simple(SimpleColumnType::Integer),
4250+
nullable: true,
4251+
default: None,
4252+
comment: None,
4253+
primary_key: None,
4254+
unique: None,
4255+
index: None,
4256+
foreign_key: None,
4257+
},
4258+
ColumnDef {
4259+
name: "pinned_tag_id".into(),
4260+
r#type: ColumnType::Simple(SimpleColumnType::Integer),
4261+
nullable: true,
4262+
default: None,
4263+
comment: None,
4264+
primary_key: None,
4265+
unique: None,
4266+
index: None,
4267+
foreign_key: None,
4268+
},
4269+
],
4270+
constraints: vec![TableConstraint::ForeignKey {
4271+
name: None,
4272+
columns: vec!["pinned_user_id".into(), "pinned_tag_id".into()],
4273+
ref_table: "user_tag".into(),
4274+
ref_columns: vec!["user_id".into(), "tag_id".into()],
4275+
on_delete: None,
4276+
on_update: None,
4277+
}],
4278+
};
4279+
4280+
let user_tag = TableDef {
4281+
name: "user_tag".into(),
4282+
description: None,
4283+
columns: vec![
4284+
ColumnDef {
4285+
name: "user_id".into(),
4286+
r#type: ColumnType::Simple(SimpleColumnType::Integer),
4287+
nullable: false,
4288+
default: None,
4289+
comment: None,
4290+
primary_key: Some(PrimaryKeySyntax::Bool(true)),
4291+
unique: None,
4292+
index: None,
4293+
foreign_key: None,
4294+
},
4295+
ColumnDef {
4296+
name: "tag_id".into(),
4297+
r#type: ColumnType::Simple(SimpleColumnType::Integer),
4298+
nullable: false,
4299+
default: None,
4300+
comment: None,
4301+
primary_key: Some(PrimaryKeySyntax::Bool(true)),
4302+
unique: None,
4303+
index: None,
4304+
foreign_key: None,
4305+
},
4306+
],
4307+
constraints: vec![
4308+
TableConstraint::ForeignKey {
4309+
name: None,
4310+
columns: vec!["user_id".into()],
4311+
ref_table: "user".into(),
4312+
ref_columns: vec!["id".into()],
4313+
on_delete: None,
4314+
on_update: None,
4315+
},
4316+
TableConstraint::ForeignKey {
4317+
name: None,
4318+
columns: vec!["tag_id".into()],
4319+
ref_table: "tag".into(),
4320+
ref_columns: vec!["id".into()],
4321+
on_delete: None,
4322+
on_update: None,
4323+
},
4324+
],
4325+
};
4326+
4327+
let tag = TableDef {
4328+
name: "tag".into(),
4329+
description: None,
4330+
columns: vec![ColumnDef {
4331+
name: "id".into(),
4332+
r#type: ColumnType::Simple(SimpleColumnType::Integer),
4333+
nullable: false,
4334+
default: None,
4335+
comment: None,
4336+
primary_key: Some(PrimaryKeySyntax::Bool(true)),
4337+
unique: None,
4338+
index: None,
4339+
foreign_key: None,
4340+
}],
4341+
constraints: vec![],
4342+
};
4343+
4344+
let schema = vec![user.clone(), user_tag, tag];
4345+
let rendered = render_entity_with_schema(&user, &schema);
4346+
4347+
// The junction table "user_tag" appears in both forward (composite FK) and reverse (M2M junction),
4348+
// so it gets relation_enum without via/via_rel
4349+
assert!(rendered.contains("relation_enum"));
4350+
// Verify we have a has_many to user_tag with relation_enum but no via
4351+
let has_user_tag_relation_enum_without_via = rendered.lines().any(|line| {
4352+
line.contains("has_many") && line.contains("relation_enum") && !line.contains("via")
4353+
});
4354+
assert!(
4355+
has_user_tag_relation_enum_without_via,
4356+
"Expected has_many with relation_enum but no via for junction table entity, got:\n{rendered}"
4357+
);
4358+
}
4359+
41274360
#[test]
41284361
fn test_json_default_value_escapes_double_quotes() {
41294362
let table = TableDef {

0 commit comments

Comments
 (0)