@@ -200,3 +200,317 @@ impl OpenApi {
200200 Ok ( ( ) )
201201 }
202202}
203+
204+ #[ cfg( test) ]
205+ mod tests {
206+ use super :: * ;
207+ use crate :: route:: { Operation , PathItem } ;
208+ use crate :: schema:: { Components , Schema , SchemaType , SecurityScheme , SecuritySchemeType } ;
209+
210+ fn create_base_openapi ( ) -> OpenApi {
211+ OpenApi {
212+ openapi : OpenApiVersion :: V3_1_0 ,
213+ info : Info {
214+ title : "Base API" . to_string ( ) ,
215+ version : "1.0.0" . to_string ( ) ,
216+ description : None ,
217+ terms_of_service : None ,
218+ contact : None ,
219+ license : None ,
220+ summary : None ,
221+ } ,
222+ servers : None ,
223+ paths : BTreeMap :: new ( ) ,
224+ components : None ,
225+ security : None ,
226+ tags : None ,
227+ external_docs : None ,
228+ }
229+ }
230+
231+ fn create_path_item ( summary : & str ) -> PathItem {
232+ PathItem {
233+ get : Some ( Operation {
234+ summary : Some ( summary. to_string ( ) ) ,
235+ description : None ,
236+ operation_id : None ,
237+ tags : None ,
238+ parameters : None ,
239+ request_body : None ,
240+ responses : BTreeMap :: new ( ) ,
241+ security : None ,
242+ } ) ,
243+ post : None ,
244+ put : None ,
245+ delete : None ,
246+ patch : None ,
247+ options : None ,
248+ head : None ,
249+ trace : None ,
250+ parameters : None ,
251+ summary : None ,
252+ description : None ,
253+ }
254+ }
255+
256+ #[ test]
257+ fn test_merge_paths ( ) {
258+ let mut base = create_base_openapi ( ) ;
259+ base. paths
260+ . insert ( "/users" . to_string ( ) , create_path_item ( "Get users" ) ) ;
261+
262+ let mut other = create_base_openapi ( ) ;
263+ other
264+ . paths
265+ . insert ( "/posts" . to_string ( ) , create_path_item ( "Get posts" ) ) ;
266+ other
267+ . paths
268+ . insert ( "/users" . to_string ( ) , create_path_item ( "Other users" ) ) ; // Conflict
269+
270+ base. merge ( other) ;
271+
272+ // Both paths should exist
273+ assert ! ( base. paths. contains_key( "/users" ) ) ;
274+ assert ! ( base. paths. contains_key( "/posts" ) ) ;
275+ // Self takes precedence on conflict
276+ assert_eq ! (
277+ base. paths
278+ . get( "/users" )
279+ . unwrap( )
280+ . get
281+ . as_ref( )
282+ . unwrap( )
283+ . summary,
284+ Some ( "Get users" . to_string( ) )
285+ ) ;
286+ }
287+
288+ #[ test]
289+ fn test_merge_schemas ( ) {
290+ let mut base = create_base_openapi ( ) ;
291+ let mut base_schemas = BTreeMap :: new ( ) ;
292+ base_schemas. insert ( "User" . to_string ( ) , Schema :: object ( ) ) ;
293+ base. components = Some ( Components {
294+ schemas : Some ( base_schemas) ,
295+ responses : None ,
296+ parameters : None ,
297+ examples : None ,
298+ request_bodies : None ,
299+ headers : None ,
300+ security_schemes : None ,
301+ } ) ;
302+
303+ let mut other = create_base_openapi ( ) ;
304+ let mut other_schemas = BTreeMap :: new ( ) ;
305+ other_schemas. insert ( "Post" . to_string ( ) , Schema :: object ( ) ) ;
306+ other_schemas. insert ( "User" . to_string ( ) , Schema :: string ( ) ) ; // Conflict
307+ other. components = Some ( Components {
308+ schemas : Some ( other_schemas) ,
309+ responses : None ,
310+ parameters : None ,
311+ examples : None ,
312+ request_bodies : None ,
313+ headers : None ,
314+ security_schemes : None ,
315+ } ) ;
316+
317+ base. merge ( other) ;
318+
319+ let schemas = base. components . as_ref ( ) . unwrap ( ) . schemas . as_ref ( ) . unwrap ( ) ;
320+ assert ! ( schemas. contains_key( "User" ) ) ;
321+ assert ! ( schemas. contains_key( "Post" ) ) ;
322+ // Self takes precedence on conflict
323+ assert_eq ! (
324+ schemas. get( "User" ) . unwrap( ) . schema_type,
325+ Some ( SchemaType :: Object )
326+ ) ;
327+ }
328+
329+ #[ test]
330+ fn test_merge_schemas_when_self_has_no_components ( ) {
331+ let mut base = create_base_openapi ( ) ;
332+ assert ! ( base. components. is_none( ) ) ;
333+
334+ let mut other = create_base_openapi ( ) ;
335+ let mut other_schemas = BTreeMap :: new ( ) ;
336+ other_schemas. insert ( "Post" . to_string ( ) , Schema :: object ( ) ) ;
337+ other. components = Some ( Components {
338+ schemas : Some ( other_schemas) ,
339+ responses : None ,
340+ parameters : None ,
341+ examples : None ,
342+ request_bodies : None ,
343+ headers : None ,
344+ security_schemes : None ,
345+ } ) ;
346+
347+ base. merge ( other) ;
348+
349+ assert ! ( base. components. is_some( ) ) ;
350+ let schemas = base. components . as_ref ( ) . unwrap ( ) . schemas . as_ref ( ) . unwrap ( ) ;
351+ assert ! ( schemas. contains_key( "Post" ) ) ;
352+ }
353+
354+ #[ test]
355+ fn test_merge_security_schemes ( ) {
356+ let mut base = create_base_openapi ( ) ;
357+ let mut base_security_schemes = HashMap :: new ( ) ;
358+ base_security_schemes. insert (
359+ "bearerAuth" . to_string ( ) ,
360+ SecurityScheme {
361+ r#type : SecuritySchemeType :: Http ,
362+ description : None ,
363+ name : None ,
364+ r#in : None ,
365+ scheme : Some ( "bearer" . to_string ( ) ) ,
366+ bearer_format : Some ( "JWT" . to_string ( ) ) ,
367+ } ,
368+ ) ;
369+ base. components = Some ( Components {
370+ schemas : None ,
371+ responses : None ,
372+ parameters : None ,
373+ examples : None ,
374+ request_bodies : None ,
375+ headers : None ,
376+ security_schemes : Some ( base_security_schemes) ,
377+ } ) ;
378+
379+ let mut other = create_base_openapi ( ) ;
380+ let mut other_security_schemes = HashMap :: new ( ) ;
381+ other_security_schemes. insert (
382+ "apiKey" . to_string ( ) ,
383+ SecurityScheme {
384+ r#type : SecuritySchemeType :: ApiKey ,
385+ description : None ,
386+ name : Some ( "X-API-Key" . to_string ( ) ) ,
387+ r#in : Some ( "header" . to_string ( ) ) ,
388+ scheme : None ,
389+ bearer_format : None ,
390+ } ,
391+ ) ;
392+ other. components = Some ( Components {
393+ schemas : None ,
394+ responses : None ,
395+ parameters : None ,
396+ examples : None ,
397+ request_bodies : None ,
398+ headers : None ,
399+ security_schemes : Some ( other_security_schemes) ,
400+ } ) ;
401+
402+ base. merge ( other) ;
403+
404+ let security_schemes = base
405+ . components
406+ . as_ref ( )
407+ . unwrap ( )
408+ . security_schemes
409+ . as_ref ( )
410+ . unwrap ( ) ;
411+ assert ! ( security_schemes. contains_key( "bearerAuth" ) ) ;
412+ assert ! ( security_schemes. contains_key( "apiKey" ) ) ;
413+ }
414+
415+ #[ test]
416+ fn test_merge_tags ( ) {
417+ let mut base = create_base_openapi ( ) ;
418+ base. tags = Some ( vec ! [ Tag {
419+ name: "users" . to_string( ) ,
420+ description: Some ( "User operations" . to_string( ) ) ,
421+ external_docs: None ,
422+ } ] ) ;
423+
424+ let mut other = create_base_openapi ( ) ;
425+ other. tags = Some ( vec ! [
426+ Tag {
427+ name: "posts" . to_string( ) ,
428+ description: Some ( "Post operations" . to_string( ) ) ,
429+ external_docs: None ,
430+ } ,
431+ Tag {
432+ name: "users" . to_string( ) ,
433+ description: Some ( "Duplicate users tag" . to_string( ) ) ,
434+ external_docs: None ,
435+ } , // Duplicate
436+ ] ) ;
437+
438+ base. merge ( other) ;
439+
440+ let tags = base. tags . as_ref ( ) . unwrap ( ) ;
441+ assert_eq ! ( tags. len( ) , 2 ) ; // No duplicates
442+ assert ! ( tags. iter( ) . any( |t| t. name == "users" ) ) ;
443+ assert ! ( tags. iter( ) . any( |t| t. name == "posts" ) ) ;
444+ // Self's description takes precedence
445+ let users_tag = tags. iter ( ) . find ( |t| t. name == "users" ) . unwrap ( ) ;
446+ assert_eq ! ( users_tag. description, Some ( "User operations" . to_string( ) ) ) ;
447+ }
448+
449+ #[ test]
450+ fn test_merge_tags_when_self_has_none ( ) {
451+ let mut base = create_base_openapi ( ) ;
452+ assert ! ( base. tags. is_none( ) ) ;
453+
454+ let mut other = create_base_openapi ( ) ;
455+ other. tags = Some ( vec ! [ Tag {
456+ name: "posts" . to_string( ) ,
457+ description: None ,
458+ external_docs: None ,
459+ } ] ) ;
460+
461+ base. merge ( other) ;
462+
463+ assert ! ( base. tags. is_some( ) ) ;
464+ assert_eq ! ( base. tags. as_ref( ) . unwrap( ) . len( ) , 1 ) ;
465+ }
466+
467+ #[ test]
468+ fn test_merge_from_str ( ) {
469+ let mut base = create_base_openapi ( ) ;
470+ base. paths
471+ . insert ( "/users" . to_string ( ) , create_path_item ( "Get users" ) ) ;
472+
473+ let other_json = r#"{
474+ "openapi": "3.1.0",
475+ "info": { "title": "Other API", "version": "2.0.0" },
476+ "paths": {
477+ "/posts": { "get": { "summary": "Get posts", "responses": {} } }
478+ }
479+ }"# ;
480+
481+ let result = base. merge_from_str ( other_json) ;
482+ assert ! ( result. is_ok( ) ) ;
483+ assert ! ( base. paths. contains_key( "/users" ) ) ;
484+ assert ! ( base. paths. contains_key( "/posts" ) ) ;
485+ }
486+
487+ #[ test]
488+ fn test_merge_from_str_invalid_json ( ) {
489+ let mut base = create_base_openapi ( ) ;
490+ let invalid_json = "{ invalid json }" ;
491+
492+ let result = base. merge_from_str ( invalid_json) ;
493+ assert ! ( result. is_err( ) ) ;
494+ }
495+
496+ #[ test]
497+ fn test_merge_empty_other ( ) {
498+ let mut base = create_base_openapi ( ) ;
499+ base. paths
500+ . insert ( "/users" . to_string ( ) , create_path_item ( "Get users" ) ) ;
501+ base. tags = Some ( vec ! [ Tag {
502+ name: "users" . to_string( ) ,
503+ description: None ,
504+ external_docs: None ,
505+ } ] ) ;
506+
507+ let other = create_base_openapi ( ) ; // Empty paths, no components, no tags
508+
509+ base. merge ( other) ;
510+
511+ // Base should remain unchanged
512+ assert_eq ! ( base. paths. len( ) , 1 ) ;
513+ assert ! ( base. paths. contains_key( "/users" ) ) ;
514+ assert_eq ! ( base. tags. as_ref( ) . unwrap( ) . len( ) , 1 ) ;
515+ }
516+ }
0 commit comments