-
Notifications
You must be signed in to change notification settings - Fork 96
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #30 from rbeauchamp/feature/24
Provide support for OData containment and attribute routing
- Loading branch information
Showing
57 changed files
with
2,240 additions
and
770 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
using System; | ||
using System.Web.Http; | ||
using System.Web.Http.Dispatcher; | ||
using Owin; | ||
using Swashbuckle.Application; | ||
using SwashbuckleODataSample; | ||
|
||
namespace Swashbuckle.OData.Tests | ||
{ | ||
public static class AppBuilderExtensions | ||
{ | ||
public static HttpConfiguration GetStandardHttpConfig(this IAppBuilder appBuilder, Type targetController, Action<SwaggerDocsConfig> unitTestConfigs = null) | ||
{ | ||
var config = new HttpConfiguration | ||
{ | ||
IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always | ||
}; | ||
var server = new HttpServer(config); | ||
appBuilder.UseWebApi(server); | ||
config.EnableSwagger(c => | ||
{ | ||
// Use "SingleApiVersion" to describe a single version API. Swagger 2.0 includes an "Info" object to | ||
// hold additional metadata for an API. Version and title are required but you can also provide | ||
// additional fields by chaining methods off SingleApiVersion. | ||
// | ||
c.SingleApiVersion("v1", "A title for your API"); | ||
// Wrap the default SwaggerGenerator with additional behavior (e.g. caching) or provide an | ||
// alternative implementation for ISwaggerProvider with the CustomProvider option. | ||
// | ||
c.CustomProvider(defaultProvider => new ODataSwaggerProvider(defaultProvider, c, config)); | ||
// Apply test-specific configs | ||
unitTestConfigs?.Invoke(c); | ||
}).EnableSwaggerUi(); | ||
|
||
FormatterConfig.Register(config); | ||
|
||
config.Services.Replace(typeof (IHttpControllerSelector), new UnitTestControllerSelector(config, targetController)); | ||
|
||
return config; | ||
} | ||
} | ||
} |
161 changes: 161 additions & 0 deletions
161
Swashbuckle.OData.Tests/Containment/AccountsController.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net; | ||
using System.Web.Http; | ||
using System.Web.OData; | ||
using System.Web.OData.Extensions; | ||
using System.Web.OData.Routing; | ||
using Containment; | ||
|
||
namespace Swashbuckle.OData.Tests.Containment | ||
{ | ||
public class AccountsController : ODataController | ||
{ | ||
private static IList<Account> _accounts; | ||
|
||
public AccountsController() | ||
{ | ||
if (_accounts == null) | ||
{ | ||
_accounts = InitAccounts(); | ||
} | ||
} | ||
|
||
[EnableQuery] | ||
public IHttpActionResult Get() | ||
{ | ||
return Ok(_accounts.AsQueryable()); | ||
} | ||
|
||
[EnableQuery] | ||
public IHttpActionResult GetPayinPIs(int key) | ||
{ | ||
var payinPIs = _accounts.Single(a => a.AccountID == key).PayinPIs; | ||
return Ok(payinPIs); | ||
} | ||
|
||
[EnableQuery] | ||
[ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")] | ||
public IHttpActionResult GetSinglePayinPI(int accountId, int paymentInstrumentId) | ||
{ | ||
var payinPIs = _accounts.Single(a => a.AccountID == accountId).PayinPIs; | ||
var payinPI = payinPIs.Single(pi => pi.PaymentInstrumentID == paymentInstrumentId); | ||
return Ok(payinPI); | ||
} | ||
|
||
[EnableQuery] | ||
public IHttpActionResult GetPayoutPI(int key, int piKey) | ||
{ | ||
var payoutPI = _accounts.Single(a => a.AccountID == key).PayoutPI; | ||
return Ok(payoutPI); | ||
} | ||
|
||
// POST ~/Accounts(100)/PayinPIs | ||
public IHttpActionResult PostToPayinPIsFromAccount(int key, PaymentInstrument pi) | ||
{ | ||
var account = _accounts.Single(a => a.AccountID == key); | ||
pi.PaymentInstrumentID = account.PayinPIs.Max(p => p.PaymentInstrumentID) + 1; | ||
account.PayinPIs.Add(pi); | ||
return Created(pi); | ||
} | ||
|
||
// PUT ~/Accounts(100)/PayoutPI | ||
[ODataRoute("Accounts({accountId})/PayoutPI")] | ||
public IHttpActionResult PutToPayoutPIFromAccount(int accountId, [FromBody] PaymentInstrument paymentInstrument) | ||
{ | ||
var account = _accounts.Single(a => a.AccountID == accountId); | ||
account.PayoutPI = paymentInstrument; | ||
return Ok(paymentInstrument); | ||
} | ||
|
||
// PUT ~/Accounts(100)/PayinPIs(101) | ||
[ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")] | ||
public IHttpActionResult PutToPayinPI(int accountId, int paymentInstrumentId, [FromBody] PaymentInstrument paymentInstrument) | ||
{ | ||
var account = _accounts.Single(a => a.AccountID == accountId); | ||
var originalPi = account.PayinPIs.Single(p => p.PaymentInstrumentID == paymentInstrumentId); | ||
originalPi.FriendlyName = paymentInstrument.FriendlyName; | ||
return Ok(paymentInstrument); | ||
} | ||
|
||
// DELETE ~/Accounts(100)/PayinPIs(101) | ||
[ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")] | ||
public IHttpActionResult DeletePayinPIFromAccount(int accountId, int paymentInstrumentId) | ||
{ | ||
var account = _accounts.Single(a => a.AccountID == accountId); | ||
var originalPi = account.PayinPIs.Single(p => p.PaymentInstrumentID == paymentInstrumentId); | ||
if (account.PayinPIs.Remove(originalPi)) | ||
{ | ||
return StatusCode(HttpStatusCode.NoContent); | ||
} | ||
return StatusCode(HttpStatusCode.InternalServerError); | ||
} | ||
|
||
// DELETE ~/Accounts(100)/PayinPIs(101) | ||
[ODataRoute("Accounts({accountId})/PayoutPI")] | ||
public IHttpActionResult DeletePayoutPIFromAccount(int accountId) | ||
{ | ||
var account = _accounts.Single(a => a.AccountID == accountId); | ||
account.PayoutPI = null; | ||
return StatusCode(HttpStatusCode.NoContent); | ||
} | ||
|
||
// GET ~/Accounts(100)/PayinPIs/Namespace.GetCount) | ||
[ODataRoute("Accounts({accountId})/PayinPIs/Containment.GetCount(NameContains={name})")] | ||
public IHttpActionResult GetPayinPIsCountWhoseNameContainsGivenValue(int accountId, [FromODataUri] string name) | ||
{ | ||
var account = _accounts.Single(a => a.AccountID == accountId); | ||
var count = account.PayinPIs.Count(pi => pi.FriendlyName.Contains(name)); | ||
|
||
return Ok(count); | ||
} | ||
|
||
[ODataRoute("ResetDataSource")] | ||
public IHttpActionResult ResetDataSource() | ||
{ | ||
_accounts = InitAccounts(); | ||
return StatusCode(HttpStatusCode.NoContent); | ||
} | ||
|
||
private string GetServiceRootUri() | ||
{ | ||
var routeName = Request.ODataProperties().RouteName; | ||
var odataRoute = Configuration.Routes[routeName] as ODataRoute; | ||
var prefixName = odataRoute.RoutePrefix; | ||
var requestUri = Request.RequestUri.ToString(); | ||
var serviceRootUri = requestUri.Substring(0, requestUri.IndexOf(prefixName) + prefixName.Length); | ||
return serviceRootUri; | ||
} | ||
|
||
private static IList<Account> InitAccounts() | ||
{ | ||
var accounts = new List<Account> | ||
{ | ||
new Account | ||
{ | ||
AccountID = 100, | ||
Name = "Name100", | ||
PayoutPI = new PaymentInstrument | ||
{ | ||
PaymentInstrumentID = 100, | ||
FriendlyName = "Payout PI: Paypal" | ||
}, | ||
PayinPIs = new List<PaymentInstrument> | ||
{ | ||
new PaymentInstrument | ||
{ | ||
PaymentInstrumentID = 101, | ||
FriendlyName = "101 first PI" | ||
}, | ||
new PaymentInstrument | ||
{ | ||
PaymentInstrumentID = 102, | ||
FriendlyName = "102 second PI" | ||
} | ||
} | ||
} | ||
}; | ||
return accounts; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using System.Web.OData.Extensions; | ||
using FluentAssertions; | ||
using Microsoft.Owin.Hosting; | ||
using NUnit.Framework; | ||
using Owin; | ||
using Swashbuckle.Swagger; | ||
using SwashbuckleODataSample; | ||
|
||
namespace Swashbuckle.OData.Tests.Containment | ||
{ | ||
[TestFixture] | ||
public class ContainmentTests | ||
{ | ||
[Test] | ||
public async Task It_supports_models_that_use_containment() | ||
{ | ||
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(AccountsController)))) | ||
{ | ||
// Arrange | ||
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress, ODataConfig.ODataRoutePrefix); | ||
|
||
// Act | ||
var swaggerDocument = await httpClient.GetJsonAsync<SwaggerDocument>("swagger/docs/v1"); | ||
|
||
// Assert | ||
PathItem pathItem; | ||
swaggerDocument.paths.TryGetValue("/odata/Accounts", out pathItem); | ||
pathItem.Should().NotBeNull(); | ||
|
||
await ValidationUtils.ValidateSwaggerJson(); | ||
} | ||
} | ||
|
||
[Test] | ||
public async Task It_supports_odata_attribute_routing() | ||
{ | ||
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(AccountsController)))) | ||
{ | ||
// Arrange | ||
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress, ODataConfig.ODataRoutePrefix); | ||
|
||
// Act | ||
var swaggerDocument = await httpClient.GetJsonAsync<SwaggerDocument>("swagger/docs/v1"); | ||
|
||
// Assert | ||
PathItem pathItem; | ||
swaggerDocument.paths.TryGetValue("/odata/Accounts({accountId})/PayinPIs({paymentInstrumentId})", out pathItem); | ||
pathItem.Should().NotBeNull(); | ||
|
||
await ValidationUtils.ValidateSwaggerJson(); | ||
} | ||
} | ||
|
||
private static void Configuration(IAppBuilder appBuilder, Type targetController) | ||
{ | ||
var config = appBuilder.GetStandardHttpConfig(targetController); | ||
|
||
config.MapODataServiceRoute("odata", "odata", ODataModels.GetModel()); | ||
|
||
config.EnsureInitialized(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
using System.Collections.Generic; | ||
using System.Web.OData.Builder; | ||
|
||
namespace Containment | ||
{ | ||
public class Account | ||
{ | ||
public int AccountID { get; set; } | ||
public string Name { get; set; } | ||
|
||
[Contained] | ||
public IList<PaymentInstrument> PayinPIs { get; set; } | ||
|
||
[Contained] | ||
public PaymentInstrument PayoutPI { get; set; } | ||
} | ||
|
||
public class PaymentInstrument | ||
{ | ||
public int PaymentInstrumentID { get; set; } | ||
public string FriendlyName { get; set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
using System.Web.OData.Builder; | ||
using Containment; | ||
using Microsoft.OData.Edm; | ||
|
||
namespace Swashbuckle.OData.Tests.Containment | ||
{ | ||
public class ODataModels | ||
{ | ||
public static IEdmModel GetModel() | ||
{ | ||
var builder = new ODataConventionModelBuilder(); | ||
var paymentInstrumentType = builder.EntityType<PaymentInstrument>(); | ||
|
||
builder.EntitySet<Account>("Accounts"); | ||
|
||
var functionConfiguration = paymentInstrumentType.Collection.Function("GetCount"); | ||
functionConfiguration.Parameter<string>("NameContains"); | ||
functionConfiguration.Returns<int>(); | ||
|
||
builder.Action("ResetDataSource"); | ||
|
||
builder.Namespace = typeof (Account).Namespace; | ||
|
||
return builder.GetEdmModel(); | ||
} | ||
} | ||
} |
Oops, something went wrong.