Skip to content

Commit f648310

Browse files
Initial set of acceptance tests (#32)
1 parent 144ed88 commit f648310

File tree

92 files changed

+3262
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

92 files changed

+3262
-0
lines changed

ApiVersioning.sln

+14
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Conventions", "Conventions"
9797
src\Common\Versioning\Conventions\IApiVersionConventionT.cs = src\Common\Versioning\Conventions\IApiVersionConventionT.cs
9898
EndProjectSection
9999
EndProject
100+
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.WebApi.Acceptance.Tests", "test\Microsoft.AspNet.WebApi.Acceptance.Tests\Microsoft.AspNet.WebApi.Acceptance.Tests.xproj", "{5C31964D-EA8B-420B-9297-5ADFEFE54962}"
101+
EndProject
102+
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Mvc.Acceptance.Tests", "test\Microsoft.AspNetCore.Mvc.Acceptance.Tests\Microsoft.AspNetCore.Mvc.Acceptance.Tests.xproj", "{4EED304C-D1A6-4866-8D7F-450D084FD25D}"
103+
EndProject
100104
Global
101105
GlobalSection(SolutionConfigurationPlatforms) = preSolution
102106
Debug|Any CPU = Debug|Any CPU
@@ -127,6 +131,14 @@ Global
127131
{D87E54CC-C2D6-4AE5-806D-AE825B051C66}.Debug|Any CPU.Build.0 = Debug|Any CPU
128132
{D87E54CC-C2D6-4AE5-806D-AE825B051C66}.Release|Any CPU.ActiveCfg = Release|Any CPU
129133
{D87E54CC-C2D6-4AE5-806D-AE825B051C66}.Release|Any CPU.Build.0 = Release|Any CPU
134+
{5C31964D-EA8B-420B-9297-5ADFEFE54962}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
135+
{5C31964D-EA8B-420B-9297-5ADFEFE54962}.Debug|Any CPU.Build.0 = Debug|Any CPU
136+
{5C31964D-EA8B-420B-9297-5ADFEFE54962}.Release|Any CPU.ActiveCfg = Release|Any CPU
137+
{5C31964D-EA8B-420B-9297-5ADFEFE54962}.Release|Any CPU.Build.0 = Release|Any CPU
138+
{4EED304C-D1A6-4866-8D7F-450D084FD25D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
139+
{4EED304C-D1A6-4866-8D7F-450D084FD25D}.Debug|Any CPU.Build.0 = Debug|Any CPU
140+
{4EED304C-D1A6-4866-8D7F-450D084FD25D}.Release|Any CPU.ActiveCfg = Release|Any CPU
141+
{4EED304C-D1A6-4866-8D7F-450D084FD25D}.Release|Any CPU.Build.0 = Release|Any CPU
130142
EndGlobalSection
131143
GlobalSection(SolutionProperties) = preSolution
132144
HideSolutionNode = FALSE
@@ -144,5 +156,7 @@ Global
144156
{AEB074E1-E57A-4DD3-A972-3625B367CE5D} = {0987757E-4D09-4523-B9C9-65B1E8832AA1}
145157
{D87E54CC-C2D6-4AE5-806D-AE825B051C66} = {0987757E-4D09-4523-B9C9-65B1E8832AA1}
146158
{B24995FB-AF48-4E5D-9327-377A599BDE2A} = {DE4EE45F-F8EA-4B32-B16F-441F946ACEF4}
159+
{5C31964D-EA8B-420B-9297-5ADFEFE54962} = {0987757E-4D09-4523-B9C9-65B1E8832AA1}
160+
{4EED304C-D1A6-4866-8D7F-450D084FD25D} = {0987757E-4D09-4523-B9C9-65B1E8832AA1}
147161
EndGlobalSection
148162
EndGlobal
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
namespace Microsoft.Web
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Net.Http;
6+
using System.Net.Http.Formatting;
7+
using System.Net.Http.Headers;
8+
using System.Threading.Tasks;
9+
using System.Web.Http;
10+
using System.Web.Http.Dispatcher;
11+
using Xunit;
12+
using static System.Net.Http.HttpMethod;
13+
using static System.String;
14+
using static System.Web.Http.IncludeErrorDetailPolicy;
15+
16+
[Trait( "Kind", "Acceptance" )]
17+
public abstract class AcceptanceTest : IDisposable
18+
{
19+
private sealed class FilteredControllerTypeResolver : List<Type>, IHttpControllerTypeResolver
20+
{
21+
public ICollection<Type> GetControllerTypes( IAssembliesResolver assembliesResolver ) => this;
22+
}
23+
24+
private const string JsonMediaType = "application/json";
25+
private static readonly HttpMethod Patch = new HttpMethod( "PATCH" );
26+
private readonly FilteredControllerTypeResolver filteredControllerTypes = new FilteredControllerTypeResolver();
27+
private bool disposed;
28+
29+
~AcceptanceTest()
30+
{
31+
Dispose( false );
32+
}
33+
34+
protected AcceptanceTest()
35+
{
36+
Configuration.IncludeErrorDetailPolicy = Always;
37+
Configuration.Services.Replace( typeof( IHttpControllerTypeResolver ), FilteredControllerTypes );
38+
Server = new HttpServer( Configuration );
39+
Client = new HttpClient( new HttpSimulatorHandler( Server ) )
40+
{
41+
BaseAddress = new Uri( "http://localhost" ),
42+
DefaultRequestHeaders =
43+
{
44+
{ "Host", "localhost" }
45+
}
46+
};
47+
}
48+
49+
protected HttpConfiguration Configuration { get; } = new HttpConfiguration();
50+
51+
protected HttpServer Server { get; }
52+
53+
protected HttpClient Client { get; }
54+
55+
protected IList<Type> FilteredControllerTypes => filteredControllerTypes;
56+
57+
protected virtual void Dispose( bool disposing )
58+
{
59+
if ( disposed )
60+
{
61+
return;
62+
}
63+
64+
disposed = true;
65+
66+
if ( !disposing )
67+
{
68+
return;
69+
}
70+
71+
Client.Dispose();
72+
Server.Dispose();
73+
Configuration.Dispose();
74+
}
75+
76+
public void Dispose()
77+
{
78+
Dispose( true );
79+
GC.SuppressFinalize( this );
80+
}
81+
82+
private HttpRequestMessage CreateRequest<TEntity>( string requestUri, TEntity entity, HttpMethod method )
83+
{
84+
var request = new HttpRequestMessage( method, requestUri );
85+
86+
if ( !Equals( entity, default( TEntity ) ) )
87+
{
88+
var formatter = new JsonMediaTypeFormatter();
89+
request.Content = new ObjectContent<TEntity>( entity, formatter, JsonMediaType );
90+
}
91+
92+
Client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue( JsonMediaType ) );
93+
94+
return request;
95+
}
96+
97+
protected void Accept( string metadata = null )
98+
{
99+
var mediaType = new MediaTypeWithQualityHeaderValue( JsonMediaType );
100+
var odataMetadata = new NameValueHeaderValue( "odata.metadata" );
101+
102+
if ( IsNullOrEmpty( metadata ) )
103+
{
104+
odataMetadata.Value = "none";
105+
}
106+
else
107+
{
108+
switch ( metadata.ToUpperInvariant() )
109+
{
110+
case "NONE":
111+
case "MINIMAL":
112+
case "FULL":
113+
break;
114+
default:
115+
throw new ArgumentOutOfRangeException( nameof( metadata ), "The specified metadata value must be 'none', 'minimal', or 'full'." );
116+
}
117+
118+
odataMetadata.Value = metadata;
119+
}
120+
121+
mediaType.Parameters.Add( odataMetadata );
122+
Client.DefaultRequestHeaders.Accept.Clear();
123+
Client.DefaultRequestHeaders.Accept.Add( mediaType );
124+
}
125+
126+
protected void PreferNoReturn() => Client.DefaultRequestHeaders.Add( "Prefer", "return=representation" );
127+
128+
protected virtual Task<HttpResponseMessage> GetAsync( string requestUri ) => Client.SendAsync( CreateRequest( requestUri, default( object ), Get ) );
129+
130+
protected virtual Task<HttpResponseMessage> PostAsync<TEntity>( string requestUri, TEntity entity ) => Client.SendAsync( CreateRequest( requestUri, entity, Post ) );
131+
132+
protected virtual Task<HttpResponseMessage> PutAsync<TEntity>( string requestUri, TEntity entity ) => Client.SendAsync( CreateRequest( requestUri, entity, Put ) );
133+
134+
protected virtual Task<HttpResponseMessage> PatchAsync<TEntity>( string requestUri, TEntity entity ) => Client.SendAsync( CreateRequest( requestUri, entity, Patch ) );
135+
136+
protected virtual Task<HttpResponseMessage> DeleteAsync( string requestUri ) => Client.SendAsync( CreateRequest( requestUri, default( object ), Delete ) );
137+
}
138+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
namespace Microsoft.Web.Http.Basic
2+
{
3+
using Controllers;
4+
using Microsoft.Web.Http.Routing;
5+
using System.Web.Http;
6+
using System.Web.Http.Routing;
7+
8+
public abstract class BasicAcceptanceTest : AcceptanceTest
9+
{
10+
protected BasicAcceptanceTest()
11+
{
12+
var constraintResolver = new DefaultInlineConstraintResolver()
13+
{
14+
ConstraintMap = { ["apiVersion"] = typeof( ApiVersionRouteConstraint ) }
15+
};
16+
17+
FilteredControllerTypes.Add( typeof( ValuesController ) );
18+
FilteredControllerTypes.Add( typeof( Values2Controller ) );
19+
FilteredControllerTypes.Add( typeof( HelloWorldController ) );
20+
Configuration.AddApiVersioning( options => options.ReportApiVersions = true );
21+
Configuration.MapHttpAttributeRoutes( constraintResolver );
22+
Configuration.EnsureInitialized();
23+
}
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace Microsoft.Web.Http.Basic.Controllers
2+
{
3+
using Microsoft.Web.Http;
4+
using System.Web.Http;
5+
6+
[ApiVersion( "1.0" )]
7+
[RoutePrefix( "api/v{version:apiVersion}/helloworld" )]
8+
public class HelloWorldController : ApiController
9+
{
10+
[Route]
11+
public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.GetRequestedApiVersion().ToString() } );
12+
13+
[Route( "{id:int}", Name = "GetMessageById" )]
14+
public IHttpActionResult Get( int id ) => Ok( new { controller = GetType().Name, id = id, version = Request.GetRequestedApiVersion().ToString() } );
15+
16+
[Route]
17+
public IHttpActionResult Post() => CreatedAtRoute( "GetMessageById", new { id = 42 }, default( object ) );
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Microsoft.Web.Http.Basic.Controllers
2+
{
3+
using Microsoft.Web.Http;
4+
using System.Web.Http;
5+
6+
[ApiVersion( "2.0" )]
7+
[Route( "api/values" )]
8+
public class Values2Controller : ApiController
9+
{
10+
public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.GetRequestedApiVersion().ToString() } );
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Microsoft.Web.Http.Basic.Controllers
2+
{
3+
using Microsoft.Web.Http;
4+
using System.Web.Http;
5+
6+
[ApiVersion( "1.0" )]
7+
[Route( "api/values" )]
8+
public class ValuesController : ApiController
9+
{
10+
public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.GetRequestedApiVersion().ToString() } );
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
namespace given
2+
{
3+
using FluentAssertions;
4+
using Microsoft.Web;
5+
using Microsoft.Web.Http.Basic;
6+
using Microsoft.Web.Http.Basic.Controllers;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using System.Net.Http;
10+
using System.Threading.Tasks;
11+
using Xunit;
12+
using static System.Net.HttpStatusCode;
13+
14+
public class _a_query_string_versioned_ApiController_split_into_two_types : BasicAcceptanceTest
15+
{
16+
[Theory]
17+
[InlineData( nameof( ValuesController ), "1.0" )]
18+
[InlineData( nameof( Values2Controller ), "2.0" )]
19+
public async Task _get_should_return_200( string controller, string apiVersion )
20+
{
21+
// arrange
22+
23+
24+
// act
25+
var response = await GetAsync( $"api/values?api-version={apiVersion}" ).EnsureSuccessStatusCode();
26+
var content = await response.Content.ReadAsAsync<IDictionary<string, string>>();
27+
28+
// assert
29+
response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1.0, 2.0" );
30+
content.ShouldBeEquivalentTo(
31+
new Dictionary<string, string>()
32+
{
33+
["controller"] = controller,
34+
["version"] = apiVersion
35+
} );
36+
}
37+
38+
[Fact]
39+
public async Task _get_should_return_400_when_version_is_unsupported()
40+
{
41+
// arrange
42+
43+
44+
// act
45+
var response = await GetAsync( "api/values?api-version=3.0" );
46+
var content = await response.Content.ReadAsAsync<OneApiErrorResponse>();
47+
48+
// assert
49+
response.StatusCode.Should().Be( BadRequest );
50+
content.Error.Code.Should().Be( "UnsupportedApiVersion" );
51+
}
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
namespace given
2+
{
3+
using FluentAssertions;
4+
using Microsoft.Web;
5+
using Microsoft.Web.Http.Basic;
6+
using Microsoft.Web.Http.Basic.Controllers;
7+
using System;
8+
using System.Collections.Generic;
9+
using System.Linq;
10+
using System.Net.Http;
11+
using System.Threading.Tasks;
12+
using Xunit;
13+
using static System.Net.HttpStatusCode;
14+
15+
public class _a_url_versioned_ApiController : BasicAcceptanceTest
16+
{
17+
[Theory]
18+
[InlineData( "api/v1/helloworld", null )]
19+
[InlineData( "api/v1/helloworld/42", "42" )]
20+
public async Task _get_should_return_200( string requestUrl, string id )
21+
{
22+
// arrange
23+
var body = new Dictionary<string, string>()
24+
{
25+
["controller"] = nameof( HelloWorldController ),
26+
["version"] = "1"
27+
};
28+
29+
if ( !string.IsNullOrEmpty( id ) )
30+
{
31+
body["id"] = id;
32+
}
33+
34+
// act
35+
var response = await GetAsync( requestUrl ).EnsureSuccessStatusCode();
36+
var content = await response.Content.ReadAsAsync<IDictionary<string, string>>();
37+
38+
// assert
39+
response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1.0" );
40+
content.ShouldBeEquivalentTo( body );
41+
}
42+
43+
[Fact]
44+
public async Task _post_should_return_201()
45+
{
46+
// arrange
47+
var entity = default( object );
48+
49+
// act
50+
var response = await PostAsync( "api/v1/helloworld", entity ).EnsureSuccessStatusCode();
51+
52+
// assert
53+
response.Headers.Location.Should().Be( new Uri( "http://localhost/api/v1/helloworld/42" ) );
54+
}
55+
56+
[Fact]
57+
public async Task _get_should_return_400_when_version_is_unsupported()
58+
{
59+
// arrange
60+
61+
62+
// act
63+
var response = await GetAsync( "api/v2/helloworld" );
64+
var content = await response.Content.ReadAsAsync<OneApiErrorResponse>();
65+
66+
// assert
67+
response.StatusCode.Should().Be( BadRequest );
68+
content.Error.Code.Should().Be( "UnsupportedApiVersion" );
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)