Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Need help with creating new tests #1119

mikekistler started this conversation in General
Jan 23, 2025 · 2 comments · 1 reply
Discussion options

Hello! I just finished adding a new API version to the eShop demo app, and in the process I learned a ton about how to implement versioning with this package. This is a great package!

But I did notice a few places where I think some small improvements would be useful. One example is that the "api-supported-versions" header is currently not included in the error response for an unspecified API version.

I can open issues for these, but was hoping to do more and actually propose a fix (this seems like it would be a relatively easy fix). But I'd like to start by adding a test that demonstrates the problem, and I can't figure out where such a test would go or how it would be written.

I believe the fix needs to go in

src\AspNetCore\WebApi\src\Asp.Versioning.Http\Routing\UnspecifiedApiVersionEndpoint.cs

but I don't see any unit tests for this class and the class is internal so not currently visible to the tests. I suppose I could use an attribute to make it visible, but I don't see that being done anywhere else in the repo so I'm reluctant to go that route.

Any guidance on how to proceed would be appreciated.

You must be logged in to vote

Replies: 2 comments · 1 reply

Comment options

Thank you for the kind words and interest. This particular case seems easy and straight forward, but it's actually more complex than it appears on the surface. The reason that you may not see api-supported-versions in this scenario has more to do with how things connect to routing. The long and short of it is that the API version comes into play before the route is evaluated. This means that api-supported-versions can't really be reported because we don't know which route is going to be matched - yet.

I don't have visualization offhand, however, if you run the acceptance tests, most of them call through to:

private static string GenerateEndpointDirectedGraph( IServiceProvider services )

This will output a link that you can open in a browser, which shows the directed graph of the route table. Hopefully, you see that without a version, you don't know which API (e.g. endpoint) is to be matched. The routing table can tell there is a candidate that is versioned, which is how you land on the error endpoint. Without knowing which endpoint (or set of endpoints) you'll land on, there's a chance you'd report the wrong versions.

Hopefully that makes sense. I'm happy to answer more questions. I suspect the test-generated graphs will provide a useful visual. If you have other ideas or suggestions, I'm open to them.

You must be logged in to vote
0 replies
Comment options

Is this situation specific to ASP.NET Core? I ask because it appears that for ASP.NET, the api-supported-versions header is included in a 400 response for Unspecified API version. The code that does this is in the HttpResponseExceptionFactory.cs.

And I confirmed that this does actually make it into the response by adding an assert in this test
:

image

This test passes with my modification.

So given this seems to work in ASP.NET, shouldn't it be possible to also make it work in ASP.NET Core?

You must be logged in to vote
1 reply
@commonsensesoftware
Comment options

Yes, it is - now - specific to ASP.NET Core. Prior to 6.0, the both behaved the same way. Other original high-level concept was to allow the routing system to do its magic and then disambiguate the results by API version. In many cases, if an API version wasn't specified, you still had the candidates whittled down and you could return a sensible set of supported API versions. Unfortunately, this approach had a number of problems with the legacy routing system (a la IRouter) and, even with Endpoint Routing, resulted in the wrong behavior for certain scenarios; specifically around 405, 406, and 415. In 6.0, this was changed so that API version itself is part of the decision process in the route tree. API Versioning does not know if your APIs have symmetric version numbers. If they don't, reporting the aggregation of API versions could easily return a lie.

This highlights where the problem occurs:

private static ApiVersionPolicyFeature? NewPolicyFeature(

In theory, it might be possible for an unspecified API version to follow the same approach as an unsupported API version. I can't recall 100% off the top of my head.

This shows how the destination is selected:

public override int GetDestination( HttpContext httpContext )

Here's an example of one of the 6 [label="HTTP: GET"] 8 -> 7 [label="HTTP: *"] 8 [label="/api/Values/helloworld/{...}/ VER: 1.0"] 9 [label="/api/Values/helloworld/{...}/ VER: 2.0 HTTP: GET"] 10 [label="/api/Values/helloworld/{...}/ VER: 2.0 HTTP: *"] 11 -> 9 [label="HTTP: GET"] 11 -> 10 [label="HTTP: *"] 11 [label="/api/Values/helloworld/{...}/ VER: 2.0"] 12 [label="/api/Values/helloworld/{...}/ VER: 3.0 HTTP: GET"] 13 [label="/api/Values/helloworld/{...}/ VER: 3.0 HTTP: *"] 14 -> 12 [label="HTTP: GET"] 14 -> 13 [label="HTTP: *"] 14 [label="/api/Values/helloworld/{...}/ VER: 3.0"] 15 -> 0 [label="VER: Malformed"] 15 -> 1 [label="VER: Ambiguous"] 15 -> 2 [label="VER: Unspecified"] 15 -> 3 [label="VER: Unsupported"] 15 -> 4 [label="VER: UnsupportedMediaType"] 15 -> 5 [label="VER: NotAcceptable"] 15 -> 8 [label="VER: 1.0"] 15 -> 11 [label="VER: 2.0"] 15 -> 14 [label="VER: 3.0"] 15 [label="/api/Values/helloworld/{...}/"] 16 [label="/api/Values/helloworld/ VER: Malformed"] 17 [label="/api/Values/helloworld/ VER: Ambiguous"] 18 [label="/api/Values/helloworld/ VER: Unspecified"] 19 [label="/api/Values/helloworld/ VER: Unsupported"] 20 [label="/api/Values/helloworld/ VER: UnsupportedMediaType"] 21 [label="/api/Values/helloworld/ VER: NotAcceptable"] 22 [label="/api/Values/helloworld/ VER: 1.0 HTTP: GET"] 23 [label="/api/Values/helloworld/ VER: 1.0 HTTP: *"] 24 -> 22 [label="HTTP: GET"] 24 -> 23 [label="HTTP: *"] 24 [label="/api/Values/helloworld/ VER: 1.0"] 25 [label="/api/Values/helloworld/ VER: 2.0 HTTP: GET"] 26 [label="/api/Values/helloworld/ VER: 2.0 HTTP: *"] 27 -> 25 [label="HTTP: GET"] 27 -> 26 [label="HTTP: *"] 27 [label="/api/Values/helloworld/ VER: 2.0"] 28 [label="/api/Values/helloworld/ VER: 3.0 HTTP: GET"] 29 [label="/api/Values/helloworld/ VER: 3.0 HTTP: *"] 30 -> 28 [label="HTTP: GET"] 30 -> 29 [label="HTTP: *"] 30 [label="/api/Values/helloworld/ VER: 3.0"] 31 -> 15 [label="/*"] 31 -> 16 [label="VER: Malformed"] 31 -> 17 [label="VER: Ambiguous"] 31 -> 18 [label="VER: Unspecified"] 31 -> 19 [label="VER: Unsupported"] 31 -> 20 [label="VER: UnsupportedMediaType"] 31 -> 21 [label="VER: NotAcceptable"] 31 -> 24 [label="VER: 1.0"] 31 -> 27 [label="VER: 2.0"] 31 -> 30 [label="VER: 3.0"] 31 [label="/api/Values/helloworld/"] 32 [label="/api/Values/{...}/ VER: Malformed"] 33 [label="/api/Values/{...}/ VER: Ambiguous"] 34 [label="/api/Values/{...}/ VER: Unspecified"] 35 [label="/api/Values/{...}/ VER: Unsupported"] 36 [label="/api/Values/{...}/ VER: UnsupportedMediaType"] 37 [label="/api/Values/{...}/ VER: NotAcceptable"] 38 [label="/api/Values/{...}/ VER: 1.0 HTTP: GET"] 39 [label="/api/Values/{...}/ VER: 1.0 HTTP: *"] 40 -> 38 [label="HTTP: GET"] 40 -> 39 [label="HTTP: *"] 40 [label="/api/Values/{...}/ VER: 1.0"] 41 [label="/api/Values/{...}/ VER: 2.0 HTTP: GET"] 42 [label="/api/Values/{...}/ VER: 2.0 HTTP: *"] 43 -> 41 [label="HTTP: GET"] 43 -> 42 [label="HTTP: *"] 43 [label="/api/Values/{...}/ VER: 2.0"] 44 [label="/api/Values/{...}/ VER: 3.0 HTTP: GET"] 45 [label="/api/Values/{...}/ VER: 3.0 HTTP: *"] 46 -> 44 [label="HTTP: GET"] 46 -> 45 [label="HTTP: *"] 46 [label="/api/Values/{...}/ VER: 3.0"] 47 -> 32 [label="VER: Malformed"] 47 -> 33 [label="VER: Ambiguous"] 47 -> 34 [label="VER: Unspecified"] 47 -> 35 [label="VER: Unsupported"] 47 -> 36 [label="VER: UnsupportedMediaType"] 47 -> 37 [label="VER: NotAcceptable"] 47 -> 40 [label="VER: 1.0"] 47 -> 43 [label="VER: 2.0"] 47 -> 46 [label="VER: 3.0"] 47 [label="/api/Values/{...}/"] 48 [label="/api/Values/ VER: Malformed"] 49 [label="/api/Values/ VER: Ambiguous"] 50 [label="/api/Values/ VER: Unspecified"] 51 [label="/api/Values/ VER: Unsupported"] 52 [label="/api/Values/ VER: UnsupportedMediaType"] 53 [label="/api/Values/ VER: NotAcceptable"] 54 [label="/api/Values/ VER: 1.0 HTTP: GET"] 55 [label="/api/Values/ VER: 1.0 HTTP: *"] 56 -> 54 [label="HTTP: GET"] 56 -> 55 [label="HTTP: *"] 56 [label="/api/Values/ VER: 1.0"] 57 [label="/api/Values/ VER: 2.0 HTTP: GET"] 58 [label="/api/Values/ VER: 2.0 HTTP: *"] 59 -> 57 [label="HTTP: GET"] 59 -> 58 [label="HTTP: *"] 59 [label="/api/Values/ VER: 2.0"] 60 [label="/api/Values/ VER: 3.0 HTTP: GET"] 61 [label="/api/Values/ VER: 3.0 HTTP: *"] 62 -> 60 [label="HTTP: GET"] 62 -> 61 [label="HTTP: *"] 62 [label="/api/Values/ VER: 3.0"] 63 -> 31 [label="/helloworld"] 63 -> 47 [label="/*"] 63 -> 48 [label="VER: Malformed"] 63 -> 49 [label="VER: Ambiguous"] 63 -> 50 [label="VER: Unspecified"] 63 -> 51 [label="VER: Unsupported"] 63 -> 52 [label="VER: UnsupportedMediaType"] 63 -> 53 [label="VER: NotAcceptable"] 63 -> 56 [label="VER: 1.0"] 63 -> 59 [label="VER: 2.0"] 63 -> 62 [label="VER: 3.0"] 63 [label="/api/Values/"] 64 [label="/api/Orders/{...}/ VER: Malformed"] 65 [label="/api/Orders/{...}/ VER: Ambiguous"] 66 [label="/api/Orders/{...}/ VER: Unspecified"] 67 [label="/api/Orders/{...}/ VER: Unsupported"] 68 [label="/api/Orders/{...}/ VER: UnsupportedMediaType"] 69 [label="/api/Orders/{...}/ VER: NotAcceptable"] 70 [label="/api/Orders/{...}/ VER: * HTTP: DELETE"] 71 [label="/api/Orders/{...}/ VER: * HTTP: *"] 72 -> 70 [label="HTTP: DELETE"] 72 -> 71 [label="HTTP: *"] 72 [label="/api/Orders/{...}/ VER: *"] 73 [label="/api/Orders/{...}/ VER: 0.9 HTTP: PUT"] 74 [label="/api/Orders/{...}/ VER: 0.9 HTTP: GET"] 75 [label="/api/Orders/{...}/ VER: 0.9 HTTP: DELETE"] 76 [label="/api/Orders/{...}/ VER: 0.9 HTTP: *"] 77 -> 73 [label="HTTP: PUT"] 77 -> 74 [label="HTTP: GET"] 77 -> 75 [label="HTTP: DELETE"] 77 -> 76 [label="HTTP: *"] 77 [label="/api/Orders/{...}/ VER: 0.9"] 78 [label="/api/Orders/{...}/ VER: 1.0 HTTP: PUT"] 79 [label="/api/Orders/{...}/ VER: 1.0 HTTP: GET"] 80 [label="/api/Orders/{...}/ VER: 1.0 HTTP: DELETE"] 81 [label="/api/Orders/{...}/ VER: 1.0 HTTP: *"] 82 -> 78 [label="HTTP: PUT"] 82 -> 79 [label="HTTP: GET"] 82 -> 80 [label="HTTP: DELETE"] 82 -> 81 [label="HTTP: *"] 82 [label="/api/Orders/{...}/ VER: 1.0"] 83 [label="/api/Orders/{...}/ VER: 2.0 HTTP: PUT"] 84 [label="/api/Orders/{...}/ VER: 2.0 HTTP: GET"] 85 [label="/api/Orders/{...}/ VER: 2.0 HTTP: DELETE"] 86 [label="/api/Orders/{...}/ VER: 2.0 HTTP: *"] 87 -> 83 [label="HTTP: PUT"] 87 -> 84 [label="HTTP: GET"] 87 -> 85 [label="HTTP: DELETE"] 87 -> 86 [label="HTTP: *"] 87 [label="/api/Orders/{...}/ VER: 2.0"] 88 [label="/api/Orders/{...}/ VER: 3.0 HTTP: DELETE"] 89 [label="/api/Orders/{...}/ VER: 3.0 HTTP: *"] 90 -> 88 [label="HTTP: DELETE"] 90 -> 89 [label="HTTP: *"] 90 [label="/api/Orders/{...}/ VER: 3.0"] 91 -> 64 [label="VER: Malformed"] 91 -> 65 [label="VER: Ambiguous"] 91 -> 66 [label="VER: Unspecified"] 91 -> 67 [label="VER: Unsupported"] 91 -> 68 [label="VER: UnsupportedMediaType"] 91 -> 69 [label="VER: NotAcceptable"] 91 -> 72 [label="VER: *"] 91 -> 77 [label="VER: 0.9"] 91 -> 82 [label="VER: 1.0"] 91 -> 87 [label="VER: 2.0"] 91 -> 90 [label="VER: 3.0"] 91 [label="/api/Orders/{...}/"] 92 [label="/api/Orders/ VER: Malformed"] 93 [label="/api/Orders/ VER: Ambiguous"] 94 [label="/api/Orders/ VER: Unspecified"] 95 [label="/api/Orders/ VER: Unsupported"] 96 [label="/api/Orders/ VER: UnsupportedMediaType"] 97 [label="/api/Orders/ VER: NotAcceptable"] 98 [label="/api/Orders/ VER: 1.0 HTTP: POST"] 99 [label="/api/Orders/ VER: 1.0 HTTP: GET"] 100 [label="/api/Orders/ VER: 1.0 HTTP: *"] 101 -> 98 [label="HTTP: POST"] 101 -> 99 [label="HTTP: GET"] 101 -> 100 [label="HTTP: *"] 101 [label="/api/Orders/ VER: 1.0"] 102 [label="/api/Orders/ VER: 2.0 HTTP: POST"] 103 [label="/api/Orders/ VER: 2.0 HTTP: GET"] 104 [label="/api/Orders/ VER: 2.0 HTTP: *"] 105 -> 102 [label="HTTP: POST"] 105 -> 103 [label="HTTP: GET"] 105 -> 104 [label="HTTP: *"] 105 [label="/api/Orders/ VER: 2.0"] 106 -> 91 [label="/*"] 106 -> 92 [label="VER: Malformed"] 106 -> 93 [label="VER: Ambiguous"] 106 -> 94 [label="VER: Unspecified"] 106 -> 95 [label="VER: Unsupported"] 106 -> 96 [label="VER: UnsupportedMediaType"] 106 -> 97 [label="VER: NotAcceptable"] 106 -> 101 [label="VER: 1.0"] 106 -> 105 [label="VER: 2.0"] 106 [label="/api/Orders/"] 107 [label="/api/{...}/helloworld/{...}/ VER: Malformed"] 108 [label="/api/{...}/helloworld/{...}/ VER: Ambiguous"] 109 [label="/api/{...}/helloworld/{...}/ VER: Unspecified"] 110 [label="/api/{...}/helloworld/{...}/ VER: Unsupported"] 111 [label="/api/{...}/helloworld/{...}/ VER: UnsupportedMediaType"] 112 [label="/api/{...}/helloworld/{...}/ VER: NotAcceptable"] 113 [label="/api/{...}/helloworld/{...}/ VER: 1.0 HTTP: GET"] 114 [label="/api/{...}/helloworld/{...}/ VER: 1.0 HTTP: *"] 115 -> 113 [label="HTTP: GET"] 115 -> 114 [label="HTTP: *"] 115 [label="/api/{...}/helloworld/{...}/ VER: 1.0"] 116 [label="/api/{...}/helloworld/{...}/ VER: 2.0 HTTP: GET"] 117 [label="/api/{...}/helloworld/{...}/ VER: 2.0 HTTP: *"] 118 -> 116 [label="HTTP: GET"] 118 -> 117 [label="HTTP: *"] 118 [label="/api/{...}/helloworld/{...}/ VER: 2.0"] 119 [label="/api/{...}/helloworld/{...}/ VER: 3.0 HTTP: GET"] 120 [label="/api/{...}/helloworld/{...}/ VER: 3.0 HTTP: *"] 121 -> 119 [label="HTTP: GET"] 121 -> 120 [label="HTTP: *"] 121 [label="/api/{...}/helloworld/{...}/ VER: 3.0"] 122 -> 107 [label="VER: Malformed"] 122 -> 108 [label="VER: Ambiguous"] 122 -> 109 [label="VER: Unspecified"] 122 -> 110 [label="VER: Unsupported"] 122 -> 111 [label="VER: UnsupportedMediaType"] 122 -> 112 [label="VER: NotAcceptable"] 122 -> 115 [label="VER: 1.0"] 122 -> 118 [label="VER: 2.0"] 122 -> 121 [label="VER: 3.0"] 122 [label="/api/{...}/helloworld/{...}/"] 123 [label="/api/{...}/helloworld/ VER: Malformed"] 124 [label="/api/{...}/helloworld/ VER: Ambiguous"] 125 [label="/api/{...}/helloworld/ VER: Unspecified"] 126 [label="/api/{...}/helloworld/ VER: Unsupported"] 127 [label="/api/{...}/helloworld/ VER: UnsupportedMediaType"] 128 [label="/api/{...}/helloworld/ VER: NotAcceptable"] 129 [label="/api/{...}/helloworld/ VER: 1.0 HTTP: GET"] 130 [label="/api/{...}/helloworld/ VER: 1.0 HTTP: *"] 131 -> 129 [label="HTTP: GET"] 131 -> 130 [label="HTTP: *"] 131 [label="/api/{...}/helloworld/ VER: 1.0"] 132 [label="/api/{...}/helloworld/ VER: 2.0 HTTP: GET"] 133 [label="/api/{...}/helloworld/ VER: 2.0 HTTP: *"] 134 -> 132 [label="HTTP: GET"] 134 -> 133 [label="HTTP: *"] 134 [label="/api/{...}/helloworld/ VER: 2.0"] 135 [label="/api/{...}/helloworld/ VER: 3.0 HTTP: GET"] 136 [label="/api/{...}/helloworld/ VER: 3.0 HTTP: *"] 137 -> 135 [label="HTTP: GET"] 137 -> 136 [label="HTTP: *"] 137 [label="/api/{...}/helloworld/ VER: 3.0"] 138 -> 122 [label="/*"] 138 -> 123 [label="VER: Malformed"] 138 -> 124 [label="VER: Ambiguous"] 138 -> 125 [label="VER: Unspecified"] 138 -> 126 [label="VER: Unsupported"] 138 -> 127 [label="VER: UnsupportedMediaType"] 138 -> 128 [label="VER: NotAcceptable"] 138 -> 131 [label="VER: 1.0"] 138 -> 134 [label="VER: 2.0"] 138 -> 137 [label="VER: 3.0"] 138 [label="/api/{...}/helloworld/"] 139 -> 138 [label="/helloworld"] 139 [label="/api/{...}/"] 140 -> 63 [label="/Values"] 140 -> 106 [label="/Orders"] 140 -> 139 [label="/*"] 140 [label="/api/"] 141 -> 140 [label="/api"] 141 [label="/"] } " rel="nofollow">directed graphs that is generated by the acceptance tests. Hopefully, that will provide some visualization to the routing tree and shed some light on where the challenges are.

API Versioning doesn't know the target endpoint yet so the challenge is accurately reporting the associated versions. These are stored on the matching endpoint. What it does know is that an API Version, any version, was expected to be specified, but wasn't.

May that give you some pointers and you'll find some additional information and/or results.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
2 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.