Skip to content

Commit

Permalink
Merge pull request #50 from rbeauchamp/feature/47
Browse files Browse the repository at this point in the history
Returns correct OData response model for collections.
  • Loading branch information
Richard Beauchamp committed Jan 9, 2016
2 parents fa2b600 + df45a05 commit e61268d
Show file tree
Hide file tree
Showing 21 changed files with 374 additions and 41 deletions.
4 changes: 2 additions & 2 deletions Swashbuckle.OData.Nuget/Swashbuckle.OData.NuGet.nuproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@
<Owners>Richard Beauchamp</Owners>
<Summary>Extends Swashbuckle with OData v4 support!</Summary>
<Description>Extends Swashbuckle with OData v4 support!</Description>
<ReleaseNotes>Supports functions that accept an enum parameter.</ReleaseNotes>
<ReleaseNotes>Response model for collections is more accurate. Fixes #47, #33.</ReleaseNotes>
<ProjectUrl>https://github.com/rbeauchamp/Swashbuckle.OData</ProjectUrl>
<LicenseUrl>https://github.com/rbeauchamp/Swashbuckle.OData/blob/master/License.txt</LicenseUrl>
<Copyright>Copyright 2015</Copyright>
<Tags>Swashbuckle Swagger SwaggerUi OData Documentation Discovery Help WebApi AspNet AspNetWebApi Docs WebHost IIS</Tags>
<Version>2.10.0</Version>
<Version>2.10.1</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Swashbuckle.OData\Swashbuckle.OData.csproj" />
Expand Down
7 changes: 7 additions & 0 deletions Swashbuckle.OData.Tests/Containment/AccountsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Linq;
using System.Net;
using System.Web.Http;
using System.Web.Http.Description;
using System.Web.OData;
using System.Web.OData.Extensions;
using System.Web.OData.Routing;
Expand All @@ -28,6 +29,7 @@ public IHttpActionResult Get()
}

[EnableQuery]
[ResponseType(typeof(List<PaymentInstrument>))]
public IHttpActionResult GetPayinPIs(int key)
{
var payinPIs = _accounts.Single(a => a.AccountID == key).PayinPIs;
Expand All @@ -36,6 +38,7 @@ public IHttpActionResult GetPayinPIs(int key)

[EnableQuery]
[ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")]
[ResponseType(typeof(PaymentInstrument))]
public IHttpActionResult GetSinglePayinPI(int accountId, int paymentInstrumentId)
{
var payinPIs = _accounts.Single(a => a.AccountID == accountId).PayinPIs;
Expand All @@ -44,13 +47,15 @@ public IHttpActionResult GetSinglePayinPI(int accountId, int paymentInstrumentId
}

[EnableQuery]
[ResponseType(typeof(PaymentInstrument))]
public IHttpActionResult GetPayoutPI(int key, int piKey)
{
var payoutPI = _accounts.Single(a => a.AccountID == key).PayoutPI;
return Ok(payoutPI);
}

// POST ~/Accounts(100)/PayinPIs
[ResponseType(typeof(PaymentInstrument))]
public IHttpActionResult PostToPayinPIsFromAccount(int key, PaymentInstrument pi)
{
var account = _accounts.Single(a => a.AccountID == key);
Expand All @@ -61,6 +66,7 @@ public IHttpActionResult PostToPayinPIsFromAccount(int key, PaymentInstrument pi

// PUT ~/Accounts(100)/PayoutPI
[ODataRoute("Accounts({accountId})/PayoutPI")]
[ResponseType(typeof(PaymentInstrument))]
public IHttpActionResult PutToPayoutPIFromAccount(int accountId, [FromBody] PaymentInstrument paymentInstrument)
{
var account = _accounts.Single(a => a.AccountID == accountId);
Expand All @@ -70,6 +76,7 @@ public IHttpActionResult PutToPayoutPIFromAccount(int accountId, [FromBody] Paym

// PUT ~/Accounts(100)/PayinPIs(101)
[ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")]
[ResponseType(typeof(PaymentInstrument))]
public IHttpActionResult PutToPayinPI(int accountId, int paymentInstrumentId, [FromBody] PaymentInstrument paymentInstrument)
{
var account = _accounts.Single(a => a.AccountID == accountId);
Expand Down
5 changes: 5 additions & 0 deletions Swashbuckle.OData.Tests/Containment/ContainmentTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Web.OData.Extensions;
using FluentAssertions;
Expand Down Expand Up @@ -47,6 +48,10 @@ public async Task It_supports_odata_attribute_routing()
PathItem pathItem;
swaggerDocument.paths.TryGetValue("/odata/Accounts({accountId})/PayinPIs({paymentInstrumentId})", out pathItem);
pathItem.Should().NotBeNull();
pathItem.get.Should().NotBeNull();
pathItem.get.produces.Should().NotBeNull();
pathItem.get.produces.Count.Should().Be(1);
pathItem.get.produces.First().Should().Be("application/json");

await ValidationUtils.ValidateSwaggerJson();
}
Expand Down
25 changes: 25 additions & 0 deletions Swashbuckle.OData.Tests/Fixtures/PostTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
Expand Down Expand Up @@ -38,6 +39,30 @@ public async Task It_has_a_summary()
}
}

[Test]
public async Task It_consumes_application_json()
{
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(CustomersController))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress);

// Act
var swaggerDocument = await httpClient.GetJsonAsync<SwaggerDocument>("swagger/docs/v1");

// Assert
PathItem pathItem;
swaggerDocument.paths.TryGetValue("/odata/Customers", out pathItem);
pathItem.Should().NotBeNull();
pathItem.post.Should().NotBeNull();
pathItem.post.consumes.Should().NotBeNull();
pathItem.post.consumes.Count.Should().Be(1);
pathItem.post.consumes.First().Should().Be("application/json");

await ValidationUtils.ValidateSwaggerJson();
}
}

private static void Configuration(IAppBuilder appBuilder, Type targetController)
{
var config = appBuilder.GetStandardHttpConfig(targetController);
Expand Down
174 changes: 174 additions & 0 deletions Swashbuckle.OData.Tests/Fixtures/ResponseModelTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Description;
using System.Web.OData;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
using FluentAssertions;
using Microsoft.OData.Edm;
using Microsoft.Owin.Hosting;
using NUnit.Framework;
using Owin;
using Swashbuckle.Swagger;

namespace Swashbuckle.OData.Tests
{
[TestFixture]
public class ResponseModelTests
{
[Test]
public async Task It_produces_an_accurate_odata_response_model_for_iqueryable_return_type()
{
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(ProductResponsesController))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress);
// Verify that the OData route in the test controller is valid
var oDataResponse = await httpClient.GetJsonAsync<ODataResponse<ProductResponse>>("/odata/ProductResponses");
oDataResponse.Value.Should().NotBeNull();
oDataResponse.Value.Count.Should().Be(20);

// Act
var swaggerDocument = await httpClient.GetJsonAsync<SwaggerDocument>("swagger/docs/v1");

// Assert
PathItem pathItem;
swaggerDocument.paths.TryGetValue("/odata/ProductResponses", out pathItem);
pathItem.get.Should().NotBeNull();
pathItem.get.produces.Should().NotBeNull();
pathItem.get.produces.Count.Should().Be(1);
pathItem.get.produces.First().Should().Be("application/json");
var getResponse = pathItem.get.responses.SingleOrDefault(response => response.Key == "200");
getResponse.Should().NotBeNull();
getResponse.Value.schema.@ref.Should().Be("#/definitions/ODataResponse[ProductResponse]");
swaggerDocument.definitions.Should().ContainKey("ODataResponse[ProductResponse]");
var responseSchema = swaggerDocument.definitions["ODataResponse[ProductResponse]"];
responseSchema.Should().NotBeNull();
responseSchema.properties.Should().NotBeNull();
responseSchema.properties.Should().ContainKey("@odata.context");
responseSchema.properties["@odata.context"].type.Should().Be("string");
responseSchema.properties["value"].type.Should().Be("array");
responseSchema.properties["value"].items.Should().NotBeNull();
responseSchema.properties["value"].items.@ref.Should().Be("#/definitions/ProductResponse");

await ValidationUtils.ValidateSwaggerJson();
}
}

[Test]
public async Task It_produces_an_accurate_odata_response_model_for_list_return_type()
{
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(ProductResponsesController))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress);
// Verify that the OData route in the test controller is valid
var top10Response = await httpClient.GetJsonAsync<ODataResponse<Product5>>("/odata/ProductResponses/Default.Top10()");
top10Response.Value.Should().NotBeNull();
top10Response.Value.Count.Should().Be(10);

// Act
var swaggerDocument = await httpClient.GetJsonAsync<SwaggerDocument>("swagger/docs/v1");

// Assert
PathItem pathItem;
swaggerDocument.paths.TryGetValue("/odata/ProductResponses/Default.Top10()", out pathItem);
var getResponse = pathItem.get.responses.SingleOrDefault(response => response.Key == "200");
getResponse.Should().NotBeNull();
getResponse.Value.schema.@ref.Should().Be("#/definitions/ODataResponse[ProductResponse]");
swaggerDocument.definitions.Should().ContainKey("ODataResponse[ProductResponse]");
var responseSchema = swaggerDocument.definitions["ODataResponse[ProductResponse]"];
responseSchema.Should().NotBeNull();
responseSchema.properties.Should().NotBeNull();
responseSchema.properties.Should().ContainKey("@odata.context");
responseSchema.properties["@odata.context"].type.Should().Be("string");
responseSchema.properties["value"].type.Should().Be("array");
responseSchema.properties["value"].items.Should().NotBeNull();
responseSchema.properties["value"].items.@ref.Should().Be("#/definitions/ProductResponse");

await ValidationUtils.ValidateSwaggerJson();
}
}

private static void Configuration(IAppBuilder appBuilder, Type targetController)
{
var config = appBuilder.GetStandardHttpConfig(targetController);

// Define a route to a controller class that contains functions
config.MapODataServiceRoute("StringKeyTestsRoute", "odata", GetEdmModel());

config.EnsureInitialized();
}

private static IEdmModel GetEdmModel()
{
ODataModelBuilder builder = new ODataConventionModelBuilder();

builder.EntitySet<ProductResponse>("ProductResponses");

var productType = builder.EntityType<ProductResponse>();

// A function that returns a list
productType.Collection
.Function("Top10")
.ReturnsCollectionFromEntitySet<ProductResponse>("ProductResponses");

return builder.GetEdmModel();
}
}

public class ProductResponse
{
[Key]
public string Id { get; set; }

public string Name { get; set; }

public double Price { get; set; }
}

public class ProductResponsesController : ODataController
{
private static readonly ConcurrentDictionary<string, ProductResponse> Data;

static ProductResponsesController()
{
Data = new ConcurrentDictionary<string, ProductResponse>();
var rand = new Random();

Enumerable.Range(0, 20).Select(i => new ProductResponse
{
Id = Guid.NewGuid().ToString(),
Name = "Product " + i,
Price = rand.NextDouble() * 1000
}).ToList().ForEach(p => Data.TryAdd(p.Id, p));
}

/// <summary>
/// An action that returns an IQueryable[]
/// </summary>
[EnableQuery]
public IQueryable<ProductResponse> Get()
{
return Data.Values.AsQueryable();
}

/// <summary>
/// A function that returns a List[]
/// </summary>
[HttpGet]
[EnableQuery]
[ResponseType(typeof(List<ProductResponse>))]
public IHttpActionResult Top10()
{
var retval = Data.Values.OrderByDescending(p => p.Price).Take(10).ToList();

return Ok(retval);
}
}
}
13 changes: 10 additions & 3 deletions Swashbuckle.OData.Tests/Fixtures/RestierTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,16 @@ public async Task It_has_a_restier_get_users_response_of_type_array()
swaggerDocument.paths.TryGetValue("/restier/Users", out pathItem);
var getUsersResponse = pathItem.get.responses.SingleOrDefault(response => response.Key == "200");
getUsersResponse.Should().NotBeNull();
getUsersResponse.Value.schema.@ref.Should().BeNull();
getUsersResponse.Value.schema.items.@ref.Should().Be("#/definitions/User");
getUsersResponse.Value.schema.type.Should().Be("array");
getUsersResponse.Value.schema.@ref.Should().Be("#/definitions/ODataResponse[User]");
swaggerDocument.definitions.Should().ContainKey("ODataResponse[User]");
var responseSchema = swaggerDocument.definitions["ODataResponse[User]"];
responseSchema.Should().NotBeNull();
responseSchema.properties.Should().NotBeNull();
responseSchema.properties.Should().ContainKey("@odata.context");
responseSchema.properties["@odata.context"].type.Should().Be("string");
responseSchema.properties["value"].type.Should().Be("array");
responseSchema.properties["value"].items.Should().NotBeNull();
responseSchema.properties["value"].items.@ref.Should().Be("#/definitions/User");

await ValidationUtils.ValidateSwaggerJson();
}
Expand Down
9 changes: 0 additions & 9 deletions Swashbuckle.OData.Tests/ODataResponse.cs

This file was deleted.

2 changes: 1 addition & 1 deletion Swashbuckle.OData.Tests/Swashbuckle.OData.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@
<Compile Include="Fixtures\PutTests.cs" />
<Compile Include="Fixtures\RestierTests.cs" />
<Compile Include="Fixtures\SchemaTests.cs" />
<Compile Include="Fixtures\ResponseModelTests.cs" />
<Compile Include="Fixtures\StringTypeUrlParamTests.cs" />
<Compile Include="HttpClientUtils.cs" />
<Compile Include="Fixtures\GetTests.cs" />
Expand All @@ -171,7 +172,6 @@
<Compile Include="NorthwindAPI\Supplier.cs" />
<Compile Include="NorthwindAPI\Territory.cs" />
<Compile Include="ODataQueryOptions\ODataQueryOptionsTests.cs" />
<Compile Include="ODataResponse.cs" />
<Compile Include="ODataVersionControllerSelector.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="HttpExtensions.cs" />
Expand Down
Loading

0 comments on commit e61268d

Please sign in to comment.