@@ -1154,4 +1154,181 @@ pub fn get_config() -> Config {
11541154 let schemas = doc. components . as_ref ( ) . unwrap ( ) . schemas . as_ref ( ) . unwrap ( ) ;
11551155 assert ! ( schemas. contains_key( "Config" ) ) ;
11561156 }
1157+
1158+ // ======== Tests for uncovered lines ========
1159+
1160+ #[ test]
1161+ fn test_fallback_struct_finding_in_route_files ( ) {
1162+ // Test line 65: fallback loop that finds struct in any route file when direct search fails
1163+ let temp_dir = TempDir :: new ( ) . expect ( "Failed to create temp dir" ) ;
1164+
1165+ // Create TWO route files - struct is in second file, route references it from first
1166+ let route1_content = r#"
1167+ pub fn get_users() -> Vec<User> {
1168+ vec![]
1169+ }
1170+ "# ;
1171+ let route1_file = create_temp_file ( & temp_dir, "users.rs" , route1_content) ;
1172+
1173+ let route2_content = r#"
1174+ fn default_name() -> String {
1175+ "Guest".to_string()
1176+ }
1177+
1178+ struct User {
1179+ #[serde(default = "default_name")]
1180+ name: String,
1181+ }
1182+
1183+ pub fn get_user() -> User {
1184+ User { name: "Alice".to_string() }
1185+ }
1186+ "# ;
1187+ let route2_file = create_temp_file ( & temp_dir, "user.rs" , route2_content) ;
1188+
1189+ let mut metadata = CollectedMetadata :: new ( ) ;
1190+ // Add struct but point to route1 (which doesn't contain the struct)
1191+ // This forces the fallback loop to search other route files
1192+ metadata. structs . push ( StructMetadata {
1193+ name : "User" . to_string ( ) ,
1194+ definition : r#"struct User { #[serde(default = "default_name")] name: String }"#
1195+ . to_string ( ) ,
1196+ } ) ;
1197+ // Add BOTH routes - the first doesn't contain User struct, so fallback searches the second
1198+ metadata. routes . push ( RouteMetadata {
1199+ method : "GET" . to_string ( ) ,
1200+ path : "/users" . to_string ( ) ,
1201+ function_name : "get_users" . to_string ( ) ,
1202+ module_path : "test::users" . to_string ( ) ,
1203+ file_path : route1_file. to_string_lossy ( ) . to_string ( ) ,
1204+ signature : "fn get_users() -> Vec<User>" . to_string ( ) ,
1205+ error_status : None ,
1206+ tags : None ,
1207+ description : None ,
1208+ } ) ;
1209+ metadata. routes . push ( RouteMetadata {
1210+ method : "GET" . to_string ( ) ,
1211+ path : "/user" . to_string ( ) ,
1212+ function_name : "get_user" . to_string ( ) ,
1213+ module_path : "test::user" . to_string ( ) ,
1214+ file_path : route2_file. to_string_lossy ( ) . to_string ( ) ,
1215+ signature : "fn get_user() -> User" . to_string ( ) ,
1216+ error_status : None ,
1217+ tags : None ,
1218+ description : None ,
1219+ } ) ;
1220+
1221+ let doc = generate_openapi_doc_with_metadata ( None , None , None , & metadata) ;
1222+
1223+ // Struct should be found via fallback and processed
1224+ assert ! ( doc. components. as_ref( ) . unwrap( ) . schemas. is_some( ) ) ;
1225+ let schemas = doc. components . as_ref ( ) . unwrap ( ) . schemas . as_ref ( ) . unwrap ( ) ;
1226+ assert ! ( schemas. contains_key( "User" ) ) ;
1227+ }
1228+
1229+ #[ test]
1230+ fn test_process_default_functions_with_no_properties ( ) {
1231+ // Test line 152: early return when schema.properties is None
1232+ // This happens when a struct has no named fields (unit struct or tuple struct)
1233+ use vespera_core:: schema:: Schema ;
1234+
1235+ let struct_item: syn:: ItemStruct = syn:: parse_str ( "struct Empty;" ) . unwrap ( ) ;
1236+ let file_ast: syn:: File = syn:: parse_str ( "fn foo() {}" ) . unwrap ( ) ;
1237+ let mut schema = Schema :: object ( ) ;
1238+ schema. properties = None ; // Explicitly set to None
1239+
1240+ // This should return early without panic
1241+ process_default_functions ( & struct_item, & file_ast, & mut schema) ;
1242+
1243+ // Schema should remain unchanged
1244+ assert ! ( schema. properties. is_none( ) ) ;
1245+ }
1246+
1247+ #[ test]
1248+ fn test_extract_value_from_expr_int_parse_failure ( ) {
1249+ // Test line 253: int parse failure (overflow)
1250+ // Create an integer literal that's too large to parse as i64
1251+ // Use a literal that syn will parse but i64::parse will fail on
1252+ let expr: syn:: Expr = syn:: parse_str ( "999999999999999999999999999999" ) . unwrap ( ) ;
1253+ let value = extract_value_from_expr ( & expr) ;
1254+ assert ! ( value. is_none( ) ) ;
1255+ }
1256+
1257+ #[ test]
1258+ fn test_extract_value_from_expr_float_parse_failure ( ) {
1259+ // Test line 260: float parse failure
1260+ // Create a float literal that's too large/invalid
1261+ let expr: syn:: Expr = syn:: parse_str ( "1e999999" ) . unwrap ( ) ;
1262+ let value = extract_value_from_expr ( & expr) ;
1263+ // This may parse successfully to infinity or fail - either way should handle it
1264+ // The important thing is no panic
1265+ let _ = value;
1266+ }
1267+
1268+ #[ test]
1269+ fn test_extract_value_from_expr_method_call_with_nested_receiver ( ) {
1270+ // Test lines 275-276: recursive extraction from method call receiver
1271+ // When receiver is not a direct string literal, it tries to extract recursively
1272+ // But the recursive call also won't find a Lit, so it returns None
1273+ // This test verifies the recursive path is exercised (line 275-276)
1274+ let expr: syn:: Expr = syn:: parse_str ( r#"("hello").to_string()"# ) . unwrap ( ) ;
1275+ let value = extract_value_from_expr ( & expr) ;
1276+ // The receiver is a Paren expression - recursive call is made but returns None
1277+ // because Paren is not handled in the match
1278+ assert ! ( value. is_none( ) ) ;
1279+ }
1280+
1281+ #[ test]
1282+ fn test_extract_value_from_expr_method_call_with_non_literal_receiver ( ) {
1283+ // Test lines 275-276: recursive extraction fails for non-literal
1284+ let expr: syn:: Expr = syn:: parse_str ( r#"some_var.to_string()"# ) . unwrap ( ) ;
1285+ let value = extract_value_from_expr ( & expr) ;
1286+ // Cannot extract value from a variable
1287+ assert ! ( value. is_none( ) ) ;
1288+ }
1289+
1290+ #[ test]
1291+ fn test_extract_value_from_expr_method_call_chained_to_string ( ) {
1292+ // Test lines 275-276: another case where recursive extraction is attempted
1293+ // Chained method calls: 42.to_string() has int literal as receiver
1294+ let expr: syn:: Expr = syn:: parse_str ( r#"42.to_string()"# ) . unwrap ( ) ;
1295+ let value = extract_value_from_expr ( & expr) ;
1296+ // Line 275 recursive call extracts 42 as Number, then line 276 returns it
1297+ assert_eq ! ( value, Some ( serde_json:: Value :: Number ( 42 . into( ) ) ) ) ;
1298+ }
1299+
1300+ #[ test]
1301+ fn test_get_type_default_empty_path_segments ( ) {
1302+ // Test line 307: empty path segments returns None
1303+ // Create a type with empty path segments
1304+
1305+ // Use parse to create a valid type, then we verify the normal path works
1306+ let ty: syn:: Type = syn:: parse_str ( "::String" ) . unwrap ( ) ;
1307+ // This has segments, so it should work
1308+ let value = get_type_default ( & ty) ;
1309+ // Global path ::String still has "String" as last segment
1310+ assert ! ( value. is_some( ) ) ;
1311+
1312+ // Test reference type (non-path type)
1313+ let ref_ty: syn:: Type = syn:: parse_str ( "&str" ) . unwrap ( ) ;
1314+ let ref_value = get_type_default ( & ref_ty) ;
1315+ // Reference is not a Path type, so returns None via line 310
1316+ assert ! ( ref_value. is_none( ) ) ;
1317+ }
1318+
1319+ #[ test]
1320+ fn test_get_type_default_tuple_type ( ) {
1321+ // Test line 310: non-Path type returns None
1322+ let ty: syn:: Type = syn:: parse_str ( "(i32, String)" ) . unwrap ( ) ;
1323+ let value = get_type_default ( & ty) ;
1324+ assert ! ( value. is_none( ) ) ;
1325+ }
1326+
1327+ #[ test]
1328+ fn test_get_type_default_array_type ( ) {
1329+ // Test line 310: array type returns None
1330+ let ty: syn:: Type = syn:: parse_str ( "[i32; 3]" ) . unwrap ( ) ;
1331+ let value = get_type_default ( & ty) ;
1332+ assert ! ( value. is_none( ) ) ;
1333+ }
11571334}
0 commit comments