Skip to content

Commit 05cbd72

Browse files
committed
Add testcase
1 parent 0de71f8 commit 05cbd72

5 files changed

Lines changed: 353 additions & 1 deletion

File tree

crates/vespera_core/src/schema.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,4 +463,79 @@ mod tests {
463463
let required = schema.required.expect("required should be initialized");
464464
assert!(required.is_empty());
465465
}
466+
467+
#[test]
468+
fn serialize_minimum_whole_number_as_integer() {
469+
let schema = Schema {
470+
minimum: Some(0.0),
471+
..Schema::integer()
472+
};
473+
let json = serde_json::to_string(&schema).unwrap();
474+
// Must be "minimum":0 (integer), NOT "minimum":0.0
475+
assert!(
476+
json.contains("\"minimum\":0"),
477+
"expected integer 0, got: {json}"
478+
);
479+
assert!(
480+
!json.contains("\"minimum\":0.0"),
481+
"must not contain 0.0: {json}"
482+
);
483+
}
484+
485+
#[test]
486+
fn serialize_minimum_fractional_as_float() {
487+
let schema = Schema {
488+
minimum: Some(1.5),
489+
..Schema::number()
490+
};
491+
let json = serde_json::to_string(&schema).unwrap();
492+
assert!(
493+
json.contains("\"minimum\":1.5"),
494+
"expected 1.5, got: {json}"
495+
);
496+
}
497+
498+
#[test]
499+
fn serialize_minimum_none_omitted() {
500+
let schema = Schema::integer();
501+
let json = serde_json::to_string(&schema).unwrap();
502+
assert!(
503+
!json.contains("minimum"),
504+
"None minimum should be omitted: {json}"
505+
);
506+
}
507+
508+
#[test]
509+
fn serialize_maximum_whole_number_as_integer() {
510+
let schema = Schema {
511+
maximum: Some(100.0),
512+
..Schema::integer()
513+
};
514+
let json = serde_json::to_string(&schema).unwrap();
515+
assert!(
516+
json.contains("\"maximum\":100"),
517+
"expected integer 100, got: {json}"
518+
);
519+
assert!(
520+
!json.contains("\"maximum\":100.0"),
521+
"must not contain 100.0: {json}"
522+
);
523+
}
524+
525+
#[test]
526+
fn serialize_multiple_of_whole_number_as_integer() {
527+
let schema = Schema {
528+
multiple_of: Some(2.0),
529+
..Schema::integer()
530+
};
531+
let json = serde_json::to_string(&schema).unwrap();
532+
assert!(
533+
json.contains("\"multipleOf\":2"),
534+
"expected integer 2, got: {json}"
535+
);
536+
assert!(
537+
!json.contains("\"multipleOf\":2.0"),
538+
"must not contain 2.0: {json}"
539+
);
540+
}
466541
}

crates/vespera_macro/src/openapi_generator.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,4 +1594,73 @@ pub fn create_users() -> String {
15941594

15951595
assert!(properties.is_empty(), "Should not insert new properties");
15961596
}
1597+
1598+
#[test]
1599+
fn test_extract_schema_default_attr_with_value() {
1600+
let attrs: Vec<syn::Attribute> = vec![syn::parse_quote!(#[schema(default = "42")])];
1601+
let result = extract_schema_default_attr(&attrs);
1602+
assert_eq!(result, Some("42".to_string()));
1603+
}
1604+
1605+
#[test]
1606+
fn test_extract_schema_default_attr_no_default() {
1607+
let attrs: Vec<syn::Attribute> = vec![syn::parse_quote!(#[schema(rename = "foo")])];
1608+
let result = extract_schema_default_attr(&attrs);
1609+
assert_eq!(result, None);
1610+
}
1611+
1612+
#[test]
1613+
fn test_extract_schema_default_attr_non_schema() {
1614+
let attrs: Vec<syn::Attribute> = vec![syn::parse_quote!(#[serde(default)])];
1615+
let result = extract_schema_default_attr(&attrs);
1616+
assert_eq!(result, None);
1617+
}
1618+
1619+
#[test]
1620+
fn test_parse_default_string_to_json_value_integer() {
1621+
let result = parse_default_string_to_json_value("42");
1622+
assert_eq!(result, serde_json::Value::Number(42.into()));
1623+
}
1624+
1625+
#[test]
1626+
fn test_parse_default_string_to_json_value_float() {
1627+
let result = parse_default_string_to_json_value("3.14");
1628+
assert_eq!(result, serde_json::json!(3.14));
1629+
}
1630+
1631+
#[test]
1632+
fn test_parse_default_string_to_json_value_bool() {
1633+
let result = parse_default_string_to_json_value("true");
1634+
assert_eq!(result, serde_json::Value::Bool(true));
1635+
}
1636+
1637+
#[test]
1638+
fn test_parse_default_string_to_json_value_string_fallback() {
1639+
let result = parse_default_string_to_json_value("hello world");
1640+
assert_eq!(result, serde_json::Value::String("hello world".to_string()));
1641+
}
1642+
1643+
#[test]
1644+
fn test_process_default_functions_with_schema_default_attr() {
1645+
use vespera_core::schema::{Schema, SchemaRef};
1646+
1647+
let file_ast: syn::File = syn::parse_str("").unwrap();
1648+
let struct_item: syn::ItemStruct =
1649+
syn::parse_str(r#"pub struct Test { #[schema(default = "100")] pub count: i32 }"#)
1650+
.unwrap();
1651+
let mut schema = Schema::object();
1652+
let props = schema.properties.get_or_insert_with(BTreeMap::new);
1653+
props.insert(
1654+
"count".to_string(),
1655+
SchemaRef::Inline(Box::new(Schema::integer())),
1656+
);
1657+
process_default_functions(&struct_item, &file_ast, &mut schema);
1658+
if let Some(SchemaRef::Inline(prop_schema)) =
1659+
schema.properties.as_ref().unwrap().get("count")
1660+
{
1661+
assert_eq!(prop_schema.default, Some(serde_json::json!(100)));
1662+
} else {
1663+
panic!("Expected inline schema with default");
1664+
}
1665+
}
15971666
}

crates/vespera_macro/src/schema_macro/codegen.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,4 +446,30 @@ mod tests {
446446
assert!(output.contains("id"));
447447
assert!(output.contains("name"));
448448
}
449+
450+
#[test]
451+
fn test_schema_to_tokens_with_minimum() {
452+
let mut schema = Schema::new(SchemaType::Integer);
453+
schema.minimum = Some(0.0);
454+
let tokens = schema_to_tokens(&schema);
455+
let output = tokens.to_string();
456+
assert!(
457+
output.contains("minimum"),
458+
"should contain minimum: {output}"
459+
);
460+
assert!(output.contains("Some"), "should contain Some: {output}");
461+
}
462+
463+
#[test]
464+
fn test_schema_to_tokens_with_maximum() {
465+
let mut schema = Schema::new(SchemaType::Integer);
466+
schema.maximum = Some(255.0);
467+
let tokens = schema_to_tokens(&schema);
468+
let output = tokens.to_string();
469+
assert!(
470+
output.contains("maximum"),
471+
"should contain maximum: {output}"
472+
);
473+
assert!(output.contains("Some"), "should contain Some: {output}");
474+
}
449475
}

crates/vespera_macro/src/schema_macro/seaorm.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,6 +1173,30 @@ mod tests {
11731173
assert_eq!(result, None);
11741174
}
11751175

1176+
#[test]
1177+
fn test_extract_sea_orm_default_value_non_list_meta() {
1178+
// #[sea_orm] as a path attribute (non-Meta::List) — line 222 branch
1179+
let attrs: Vec<syn::Attribute> = vec![syn::parse_quote!(#[sea_orm])];
1180+
let result = extract_sea_orm_default_value(&attrs);
1181+
assert_eq!(result, None);
1182+
}
1183+
1184+
#[test]
1185+
fn test_extract_sea_orm_default_value_empty_value_after_equals() {
1186+
// default_value = , (empty value) — line 236 branch
1187+
let attrs: Vec<syn::Attribute> = vec![syn::parse_quote!(#[sea_orm(default_value = )])];
1188+
let result = extract_sea_orm_default_value(&attrs);
1189+
assert_eq!(result, None);
1190+
}
1191+
1192+
#[test]
1193+
fn test_extract_sea_orm_default_value_no_default_value_key() {
1194+
let attrs: Vec<syn::Attribute> =
1195+
vec![syn::parse_quote!(#[sea_orm(primary_key, auto_increment)])];
1196+
let result = extract_sea_orm_default_value(&attrs);
1197+
assert_eq!(result, None);
1198+
}
1199+
11761200
// =========================================================================
11771201
// Tests for is_sql_function_default
11781202
// =========================================================================

crates/vespera_macro/src/schema_macro/tests.rs

Lines changed: 159 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,165 @@ fn test_generate_schema_type_code_no_from_impl_with_add() {
244244
assert!(result.is_ok());
245245
let (tokens, _metadata) = result.unwrap();
246246
let output = tokens.to_string();
247-
assert!(!output.contains("impl From"));
247+
assert!(
248+
output.contains("UserWithExtra"),
249+
"expected struct UserWithExtra in output: {output}"
250+
);
251+
assert!(
252+
!output.contains("impl From"),
253+
"expected no From impl when `add` is used: {output}"
254+
);
255+
}
256+
257+
// ========================
258+
// is_parseable_type tests
259+
// ========================
260+
261+
#[test]
262+
fn test_is_parseable_type_primitives() {
263+
for ty_str in &[
264+
"i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64", "u128", "usize",
265+
"f32", "f64", "bool", "String", "Decimal",
266+
] {
267+
let ty: syn::Type = syn::parse_str(ty_str).unwrap();
268+
assert!(is_parseable_type(&ty), "{ty_str} should be parseable");
269+
}
270+
}
271+
272+
#[test]
273+
fn test_is_parseable_type_non_parseable() {
274+
let ty: syn::Type = syn::parse_str("MyEnum").unwrap();
275+
assert!(!is_parseable_type(&ty));
276+
}
277+
278+
#[test]
279+
fn test_is_parseable_type_non_path() {
280+
let ty: syn::Type = syn::parse_str("&str").unwrap();
281+
assert!(!is_parseable_type(&ty));
282+
}
283+
284+
// ======================================
285+
// generate_sea_orm_default_attrs tests
286+
// ======================================
287+
288+
#[test]
289+
fn test_sea_orm_default_attrs_optional_field_skips() {
290+
let attrs: Vec<syn::Attribute> = vec![syn::parse_quote!(#[sea_orm(default_value = "42")])];
291+
let struct_name = syn::Ident::new("Test", proc_macro2::Span::call_site());
292+
let ty: syn::Type = syn::parse_str("i32").unwrap();
293+
let mut fns = Vec::new();
294+
let (serde, schema) =
295+
generate_sea_orm_default_attrs(&attrs, &struct_name, "count", &ty, &ty, true, &mut fns);
296+
assert!(serde.is_empty());
297+
assert!(schema.is_empty());
298+
assert!(fns.is_empty());
299+
}
300+
301+
#[test]
302+
fn test_sea_orm_default_attrs_no_default_value() {
303+
let attrs: Vec<syn::Attribute> = vec![syn::parse_quote!(#[sea_orm(primary_key)])];
304+
let struct_name = syn::Ident::new("Test", proc_macro2::Span::call_site());
305+
let ty: syn::Type = syn::parse_str("i32").unwrap();
306+
let mut fns = Vec::new();
307+
let (serde, schema) =
308+
generate_sea_orm_default_attrs(&attrs, &struct_name, "id", &ty, &ty, false, &mut fns);
309+
assert!(serde.is_empty());
310+
assert!(schema.is_empty());
311+
}
312+
313+
#[test]
314+
fn test_sea_orm_default_attrs_sql_function_skips() {
315+
let attrs: Vec<syn::Attribute> = vec![syn::parse_quote!(#[sea_orm(default_value = "NOW()")])];
316+
let struct_name = syn::Ident::new("Test", proc_macro2::Span::call_site());
317+
let ty: syn::Type = syn::parse_str("String").unwrap();
318+
let mut fns = Vec::new();
319+
let (serde, schema) = generate_sea_orm_default_attrs(
320+
&attrs,
321+
&struct_name,
322+
"created_at",
323+
&ty,
324+
&ty,
325+
false,
326+
&mut fns,
327+
);
328+
assert!(serde.is_empty());
329+
assert!(schema.is_empty());
330+
}
331+
332+
#[test]
333+
fn test_sea_orm_default_attrs_existing_serde_default() {
334+
let attrs: Vec<syn::Attribute> = vec![
335+
syn::parse_quote!(#[sea_orm(default_value = "42")]),
336+
syn::parse_quote!(#[serde(default)]),
337+
];
338+
let struct_name = syn::Ident::new("Test", proc_macro2::Span::call_site());
339+
let ty: syn::Type = syn::parse_str("i32").unwrap();
340+
let mut fns = Vec::new();
341+
let (serde, schema) =
342+
generate_sea_orm_default_attrs(&attrs, &struct_name, "count", &ty, &ty, false, &mut fns);
343+
// serde attr should be empty (already has serde default)
344+
assert!(serde.is_empty());
345+
// schema attr should still be generated
346+
let schema_str = schema.to_string();
347+
assert!(
348+
schema_str.contains("schema"),
349+
"should have schema attr: {schema_str}"
350+
);
351+
assert!(
352+
fns.is_empty(),
353+
"no default fn needed when serde(default) exists"
354+
);
355+
}
356+
357+
#[test]
358+
fn test_sea_orm_default_attrs_non_parseable_type() {
359+
let attrs: Vec<syn::Attribute> = vec![syn::parse_quote!(#[sea_orm(default_value = "Active")])];
360+
let struct_name = syn::Ident::new("Test", proc_macro2::Span::call_site());
361+
let ty: syn::Type = syn::parse_str("MyEnum").unwrap();
362+
let mut fns = Vec::new();
363+
let (serde, schema) =
364+
generate_sea_orm_default_attrs(&attrs, &struct_name, "status", &ty, &ty, false, &mut fns);
365+
// serde attr empty (non-parseable type)
366+
assert!(serde.is_empty());
367+
// schema attr still generated
368+
let schema_str = schema.to_string();
369+
assert!(
370+
schema_str.contains("schema"),
371+
"should have schema attr: {schema_str}"
372+
);
373+
assert!(fns.is_empty());
374+
}
375+
376+
#[test]
377+
fn test_sea_orm_default_attrs_full_generation() {
378+
let attrs: Vec<syn::Attribute> = vec![syn::parse_quote!(#[sea_orm(default_value = "42")])];
379+
let struct_name = syn::Ident::new("Test", proc_macro2::Span::call_site());
380+
let ty: syn::Type = syn::parse_str("i32").unwrap();
381+
let mut fns = Vec::new();
382+
let (serde, schema) =
383+
generate_sea_orm_default_attrs(&attrs, &struct_name, "count", &ty, &ty, false, &mut fns);
384+
// Both serde and schema attrs should be generated
385+
let serde_str = serde.to_string();
386+
assert!(
387+
serde_str.contains("serde"),
388+
"should have serde attr: {serde_str}"
389+
);
390+
assert!(
391+
serde_str.contains("default_Test_count"),
392+
"should reference generated fn: {serde_str}"
393+
);
394+
let schema_str = schema.to_string();
395+
assert!(
396+
schema_str.contains("schema"),
397+
"should have schema attr: {schema_str}"
398+
);
399+
// Default function should be generated
400+
assert_eq!(fns.len(), 1, "should generate one default function");
401+
let fn_str = fns[0].to_string();
402+
assert!(
403+
fn_str.contains("default_Test_count"),
404+
"fn name should match: {fn_str}"
405+
);
248406
}
249407

250408
#[test]

0 commit comments

Comments
 (0)