Skip to content

Extend tests and add Swagger UI #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ node_modules
obj
packages
project.lock.json
src/TodoApp/wwwroot/**/*.js
src/TodoApp/wwwroot/**/*.map
src/TodoApp/wwwroot/static/**/*.js
src/TodoApp/wwwroot/static/**/*.map
TestResults
typings
UpgradeLog*.htm
Expand Down
4 changes: 0 additions & 4 deletions src/TodoApp/ApiModule.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Martin Costello, 2021. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using System.Security.Claims;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
Expand Down Expand Up @@ -100,9 +99,6 @@ public static IEndpointRouteBuilder MapApiRoutes(this IEndpointRouteBuilder buil
private static string GetItemId(this HttpContext context)
=> (string)context.GetRouteValue("id")!;

private static string GetUserId(this HttpContext context)
=> context.User.FindFirst(ClaimTypes.NameIdentifier)!.Value;

private static ITodoService GetTodoService(this HttpContext context)
=> context.RequestServices.GetRequiredService<ITodoService>();
}
Expand Down
6 changes: 6 additions & 0 deletions src/TodoApp/AuthenticationModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ public static IServiceCollection AddGitHubAuthentication(this IServiceCollection
public static string GetProfileUrl(this ClaimsPrincipal user)
=> user.FindFirst(GitHubProfileClaim)!.Value;

public static string GetUserId(this HttpContext context)
=> context.User.GetUserId();

public static string GetUserId(this ClaimsPrincipal user)
=> user.FindFirst(ClaimTypes.NameIdentifier)!.Value;

public static string GetUserName(this ClaimsPrincipal user)
=> user.FindFirst(GitHubAuthenticationConstants.Claims.Name)!.Value;

Expand Down
11 changes: 11 additions & 0 deletions src/TodoApp/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using NodaTime;
using TodoApp.Data;
using TodoApp.Services;
Expand Down Expand Up @@ -54,6 +55,14 @@ public void ConfigureServices(IServiceCollection services)
});

services.AddHttpClient();

services.AddMvcCore()
.AddApiExplorer();

services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Todo API", Version = "v1" });
});
}

public void Configure(IApplicationBuilder app)
Expand Down Expand Up @@ -84,6 +93,8 @@ public void Configure(IApplicationBuilder app)
endpoints.MapAuthenticationRoutes();
endpoints.MapRazorPages();
});

app.UseSwagger();
}
}
}
4 changes: 1 addition & 3 deletions src/TodoApp/TodoApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@
<TypeScriptToolsVersion>latest</TypeScriptToolsVersion>
<UserSecretsId>TodoApp</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<None Remove="scripts\ts\main.ts" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AspNet.Security.OAuth.GitHub" Version="6.0.0-preview.5.21319.40" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.0-preview.5.21301.9" />
<PackageReference Include="Microsoft.TypeScript.MSBuild" Version="4.3.2" PrivateAssets="all" />
<PackageReference Include="NodaTime" Version="3.0.5" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.4" />
</ItemGroup>
<ItemGroup>
<None Remove="scripts\ts\**\*.ts" />
Expand Down
Binary file added src/TodoApp/wwwroot/swagger-ui/favicon-16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/TodoApp/wwwroot/swagger-ui/favicon-32x32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 60 additions & 0 deletions src/TodoApp/wwwroot/swagger-ui/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>API Documentation - TodoApp</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style>
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}

*,
*:before,
*:after {
box-sizing: inherit;
}

body {
margin: 0;
background: #fafafa;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js" charset="UTF-8"></script>
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"></script>
<script>
window.onload = function () {
window.ui = SwaggerUIBundle({
url: "/swagger/v1/swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl,
{
components: {
Topbar: () => null
}
}
],
defaultModelRendering: "schema",
displayRequestDuration: true,
jsonEditor: true,
layout: "StandaloneLayout",
showRequestHeaders: true,
validatorUrl: null
});
};
</script>
</body>
</html>
3 changes: 3 additions & 0 deletions src/TodoApp/wwwroot/swagger-ui/swagger-ui-bundle.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/TodoApp/wwwroot/swagger-ui/swagger-ui-bundle.js.map

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/TodoApp/wwwroot/swagger-ui/swagger-ui.css

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/TodoApp/wwwroot/swagger-ui/swagger-ui.css.map

Large diffs are not rendered by default.

134 changes: 134 additions & 0 deletions tests/TodoApp.Tests/ApiTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright (c) Martin Costello, 2021. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.AspNetCore.Mvc.Testing;
using TodoApp.Models;

namespace TodoApp
{
[Collection(TodoAppCollection.Name)]
public class ApiTests
{
public ApiTests(TodoAppFixture fixture, ITestOutputHelper outputHelper)
{
Fixture = fixture;
OutputHelper = outputHelper;
Fixture.SetOutputHelper(OutputHelper);
}

private TodoAppFixture Fixture { get; }

private ITestOutputHelper OutputHelper { get; }

[Fact]
public async Task Can_Manage_Todo_Items_With_Api()
{
// Arrange
var client = await CreateAuthenticatedClientAsync();

// Act - Get all the items
var items = await client.GetFromJsonAsync<TodoListViewModel>("/api/items");

// Assert - There should be no items
items.ShouldNotBeNull();
items.Items.ShouldNotBeNull();

var beforeCount = items.Items.Count;

// Arrange
var text = "Buy eggs";
var newItem = new CreateTodoItemModel { Text = text };

// Act - Add a new item
using var createdResponse = await client.PostAsJsonAsync("/api/items", newItem);

// Assert - An item was created
createdResponse.StatusCode.ShouldBe(HttpStatusCode.Created);
createdResponse.Headers.Location.ShouldNotBeNull();

using var createdStream = await createdResponse.Content.ReadAsStreamAsync();
using var createdJson = await JsonDocument.ParseAsync(createdStream);

// Arrange - Get the new item's URL and Id
var itemUri = createdResponse.Headers.Location;
var itemId = createdJson.RootElement.GetProperty("id").GetString();

// Act - Get the item
var item = await client.GetFromJsonAsync<TodoItemModel>(itemUri);

// Assert - Validate the item was created correctly
item.ShouldNotBeNull();
item.Id.ShouldBe(itemId);
item.IsCompleted.ShouldBeFalse();
item.LastUpdated.ShouldNotBeNull();
item.Text.ShouldBe(text);

// Act - Mark the item as being completed
using var completedResponse = await client.PostAsJsonAsync(itemUri + "/complete", new { });

// Assert - The item was completed
completedResponse.StatusCode.ShouldBe(HttpStatusCode.NoContent);

item = await client.GetFromJsonAsync<TodoItemModel>(itemUri);

item.ShouldNotBeNull();
item.Id.ShouldBe(itemId);
item.Text.ShouldBe(text);
item.IsCompleted.ShouldBeTrue();

// Act - Get all the items
items = await client.GetFromJsonAsync<TodoListViewModel>("/api/items");

// Assert - The item was completed
items.ShouldNotBeNull();
items.Items.ShouldNotBeNull();
items.Items.Count.ShouldBe(beforeCount + 1);
item = items.Items.Last();

item.ShouldNotBeNull();
item.Id.ShouldBe(itemId);
item.Text.ShouldBe(text);
item.IsCompleted.ShouldBeTrue();
item.LastUpdated.ShouldNotBeNull();

// Act - Delete the item
using var deletedResponse = await client.DeleteAsync(itemUri);

// Assert - The item no longer exists
deletedResponse.StatusCode.ShouldBe(HttpStatusCode.NoContent);

items = await client.GetFromJsonAsync<TodoListViewModel>("/api/items");

items.ShouldNotBeNull();
items.Items.ShouldNotBeNull();
items.Items.Count.ShouldBe(beforeCount);
items.Items.ShouldNotContain(p => p.Id == itemId);

var exception = await Assert.ThrowsAsync<HttpRequestException>(
() => client.GetFromJsonAsync<TodoItemModel>(itemUri));

exception.StatusCode.ShouldBe(HttpStatusCode.NotFound);
}

private async Task<HttpClient> CreateAuthenticatedClientAsync()
{
var options = new WebApplicationFactoryClientOptions();
var client = Fixture.CreateClient(options);

var parameters = Array.Empty<KeyValuePair<string?, string?>>();
using var content = new FormUrlEncodedContent(parameters);

// Go through the sign-in flow, which will set
// the authentication cookie on the HttpClient.
using var response = await client.PostAsync("/signin", content);

response.IsSuccessStatusCode.ShouldBeTrue();

return client;
}
}
}
2 changes: 1 addition & 1 deletion tests/TodoApp.Tests/HttpServerCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ namespace TodoApp
[CollectionDefinition(Name)]
public sealed class HttpServerCollection : ICollectionFixture<HttpServerFixture>
{
public const string Name = "HTTP server collection";
public const string Name = "TodoApp HTTP server collection";
}
}
Loading