@@ -2419,5 +2419,198 @@ public async Task SerializeDocumentWithSecurityRequirementAsJsonWorks(OpenApiSpe
24192419 Assert . NotNull ( actualSecurity ) ;
24202420 Assert . True ( JsonNode . DeepEquals ( JsonNode . Parse ( expected ) , actualSecurity ) ) ;
24212421 }
2422+
2423+ [ Fact ]
2424+ public void GetOperationById_ReturnsMatchingOperation ( )
2425+ {
2426+ var operation = new OpenApiOperation { OperationId = "getUser" } ;
2427+ var doc = new OpenApiDocument
2428+ {
2429+ Info = new OpenApiInfo { Title = "Test" , Version = "1.0" } ,
2430+ Paths = new OpenApiPaths
2431+ {
2432+ [ "/users/{id}" ] = new OpenApiPathItem
2433+ {
2434+ Operations = new Dictionary < HttpMethod , OpenApiOperation >
2435+ {
2436+ [ HttpMethod . Get ] = operation
2437+ }
2438+ }
2439+ }
2440+ } ;
2441+
2442+ var result = doc . GetOperationById ( "getUser" ) ;
2443+
2444+ Assert . Same ( operation , result ) ;
2445+ }
2446+
2447+ [ Fact ]
2448+ public void GetOperationById_ReturnsNullWhenNotFound ( )
2449+ {
2450+ var doc = new OpenApiDocument
2451+ {
2452+ Info = new OpenApiInfo { Title = "Test" , Version = "1.0" } ,
2453+ Paths = new OpenApiPaths
2454+ {
2455+ [ "/users" ] = new OpenApiPathItem
2456+ {
2457+ Operations = new Dictionary < HttpMethod , OpenApiOperation >
2458+ {
2459+ [ HttpMethod . Get ] = new OpenApiOperation { OperationId = "listUsers" }
2460+ }
2461+ }
2462+ }
2463+ } ;
2464+
2465+ var result = doc . GetOperationById ( "nonExistentId" ) ;
2466+
2467+ Assert . Null ( result ) ;
2468+ }
2469+
2470+ [ Fact ]
2471+ public void GetOperationById_SearchesWebhooks ( )
2472+ {
2473+ var webhookOperation = new OpenApiOperation { OperationId = "onUserCreated" } ;
2474+ var doc = new OpenApiDocument
2475+ {
2476+ Info = new OpenApiInfo { Title = "Test" , Version = "1.0" } ,
2477+ Paths = [ ] ,
2478+ Webhooks = new Dictionary < string , IOpenApiPathItem >
2479+ {
2480+ [ "userCreated" ] = new OpenApiPathItem
2481+ {
2482+ Operations = new Dictionary < HttpMethod , OpenApiOperation >
2483+ {
2484+ [ HttpMethod . Post ] = webhookOperation
2485+ }
2486+ }
2487+ }
2488+ } ;
2489+
2490+ var result = doc . GetOperationById ( "onUserCreated" ) ;
2491+
2492+ Assert . Same ( webhookOperation , result ) ;
2493+ }
2494+
2495+ [ Fact ]
2496+ public void GetOperationById_IsCaseSensitive ( )
2497+ {
2498+ var doc = new OpenApiDocument
2499+ {
2500+ Info = new OpenApiInfo { Title = "Test" , Version = "1.0" } ,
2501+ Paths = new OpenApiPaths
2502+ {
2503+ [ "/users" ] = new OpenApiPathItem
2504+ {
2505+ Operations = new Dictionary < HttpMethod , OpenApiOperation >
2506+ {
2507+ [ HttpMethod . Get ] = new OpenApiOperation { OperationId = "getUser" }
2508+ }
2509+ }
2510+ }
2511+ } ;
2512+
2513+ Assert . NotNull ( doc . GetOperationById ( "getUser" ) ) ;
2514+ Assert . Null ( doc . GetOperationById ( "GetUser" ) ) ;
2515+ Assert . Null ( doc . GetOperationById ( "GETUSER" ) ) ;
2516+ }
2517+
2518+ [ Fact ]
2519+ public void GetOperationById_ResolvesOperationThroughPathItemReference ( )
2520+ {
2521+ const string yaml = """
2522+ openapi: '3.1.0'
2523+ info:
2524+ title: Test
2525+ version: 1.0.0
2526+ paths:
2527+ /users:
2528+ $ref: '#/components/pathItems/userPathItem'
2529+ components:
2530+ pathItems:
2531+ userPathItem:
2532+ get:
2533+ operationId: listUsers
2534+ responses:
2535+ '200':
2536+ description: OK
2537+ """ ;
2538+
2539+ var doc = OpenApiDocument . Parse ( yaml , OpenApiConstants . Yaml , SettingsFixture . ReaderSettings ) . Document ;
2540+ doc . Workspace . RegisterComponents ( doc ) ;
2541+
2542+ var result = doc . GetOperationById ( "listUsers" ) ;
2543+
2544+ Assert . NotNull ( result ) ;
2545+ Assert . Equal ( "listUsers" , result . OperationId ) ;
2546+ }
2547+
2548+ [ Fact ]
2549+ public void GetOperationById_DuplicateIdReturnsFirstMatch ( )
2550+ {
2551+ // operationId must be unique per spec, but if not, Paths takes priority over Webhooks
2552+ var pathsOperation = new OpenApiOperation { OperationId = "duplicateId" } ;
2553+ var webhooksOperation = new OpenApiOperation { OperationId = "duplicateId" } ;
2554+ var doc = new OpenApiDocument
2555+ {
2556+ Info = new OpenApiInfo { Title = "Test" , Version = "1.0" } ,
2557+ Paths = new OpenApiPaths
2558+ {
2559+ [ "/users" ] = new OpenApiPathItem
2560+ {
2561+ Operations = new Dictionary < HttpMethod , OpenApiOperation >
2562+ {
2563+ [ HttpMethod . Get ] = pathsOperation
2564+ }
2565+ }
2566+ } ,
2567+ Webhooks = new Dictionary < string , IOpenApiPathItem >
2568+ {
2569+ [ "userEvent" ] = new OpenApiPathItem
2570+ {
2571+ Operations = new Dictionary < HttpMethod , OpenApiOperation >
2572+ {
2573+ [ HttpMethod . Post ] = webhooksOperation
2574+ }
2575+ }
2576+ }
2577+ } ;
2578+
2579+ var result = doc . GetOperationById ( "duplicateId" ) ;
2580+
2581+ Assert . Same ( pathsOperation , result ) ;
2582+ }
2583+
2584+ [ Fact ]
2585+ public void GetOperationById_UnresolvedPathItemReferenceIsSkipped ( )
2586+ {
2587+ // An unresolved $ref has Target = null, so Operations = null — should be skipped gracefully
2588+ var unresolvedRef = new OpenApiPathItemReference ( "nonExistentPathItem" , null ) ;
2589+ var doc = new OpenApiDocument
2590+ {
2591+ Info = new OpenApiInfo { Title = "Test" , Version = "1.0" } ,
2592+ Paths = new OpenApiPaths
2593+ {
2594+ [ "/users" ] = unresolvedRef
2595+ }
2596+ } ;
2597+
2598+ var result = doc . GetOperationById ( "anyId" ) ;
2599+
2600+ Assert . Null ( result ) ;
2601+ }
2602+
2603+ [ Fact ]
2604+ public void GetOperationById_ThrowsOnNullOrEmptyId ( )
2605+ {
2606+ var doc = new OpenApiDocument
2607+ {
2608+ Info = new OpenApiInfo { Title = "Test" , Version = "1.0" } ,
2609+ Paths = [ ]
2610+ } ;
2611+
2612+ Assert . Throws < ArgumentNullException > ( ( ) => doc . GetOperationById ( null ! ) ) ;
2613+ Assert . Throws < ArgumentNullException > ( ( ) => doc . GetOperationById ( string . Empty ) ) ;
2614+ }
24222615 }
24232616}
0 commit comments