Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
FerroEduardo committed Oct 10, 2023
1 parent c1a6d52 commit 0041d3e
Show file tree
Hide file tree
Showing 19 changed files with 353 additions and 38 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,3 @@ jobs:
run: dotnet restore
- name: Build
run: dotnet build --no-restore
# - name: Test
# run: dotnet test --no-build --verbosity normal
35 changes: 35 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# This workflow will build a .NET project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net

name: test

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
test:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
- name: Setup SQL Server
run: docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=strong_password" -e "MSSQL_USER=nice_user" -p 1433:1433 --name=sqlserver -d mcr.microsoft.com/mssql/server:2022-latest
- name: Restore dependencies
run: dotnet restore
- name: Setup "testing.json"
run: |
cd SuperHeroAPI
echo '{ "Database": { "URL": "Server=sqlserver,1433;User ID=nice_user;password=strong_password;Database=superhero_testing;Trusted_Connection=True;TrustServerCertificate=True;" } }' > testing.json
ls -lha
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal
116 changes: 116 additions & 0 deletions IntegrationTests/AuthenticationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using SuperHeroAPI.Models.Request;
using SuperHeroAPI.Models.Response;
using SuperHeroAPI.Services;
using System.Dynamic;
using System.Net.Http.Json;

namespace IntegrationTests
{
public class AuthenticationTests
{
[Fact]
public async void Signup()
{
// Arrange
var application = new SuperHeroWebApplicationFactory();
using (var scope = application.Services.CreateScope())
{
AuthenticationRequest request = new AuthenticationRequest();
request.Username = "user";
request.Password = "password";

var client = application.CreateClient();

// Act
var response = await client.PostAsJsonAsync("/auth/signup", request);

// Assert
response.EnsureSuccessStatusCode();

var responseBody = await response.Content.ReadAsStringAsync();
responseBody.Should().NotBeNullOrEmpty();
var tokenService = scope.ServiceProvider.GetRequiredService<TokenService>();
tokenService.IsValidToken(responseBody).Should().BeTrue();
}
}

[Fact]
public async void Signin()
{
// Arrange
var application = new SuperHeroWebApplicationFactory();
using (var scope = application.Services.CreateScope())
{
AuthenticationRequest request = new AuthenticationRequest();
request.Username = "eduardo";
request.Password = "senha";

var client = application.CreateClient();

// Act
var response = await client.PostAsJsonAsync("/auth/signin", request);

// Assert
response.EnsureSuccessStatusCode();

var responseBody = await response.Content.ReadAsStringAsync();
responseBody.Should().NotBeNullOrEmpty();
var tokenService = scope.ServiceProvider.GetRequiredService<TokenService>();
tokenService.IsValidToken(responseBody).Should().BeTrue();
}
}

[Fact]
public async void Signin_WrongPassword()
{
// Arrange
var application = new SuperHeroWebApplicationFactory();
using (var scope = application.Services.CreateScope())
{
AuthenticationRequest request = new AuthenticationRequest();
request.Username = "eduardo";
request.Password = "password";

var client = application.CreateClient();

// Act
var response = await client.PostAsJsonAsync("/auth/signin", request);

// Assert
response.StatusCode.Should().Be(System.Net.HttpStatusCode.BadRequest);

var responseBody = await response.Content.ReadFromJsonAsync<ErrorResponse>();
responseBody.Should().NotBeNull();
responseBody!.Message.Should().Be("Invalid credentials.");
}
}


[Fact]
public async void Signup_UsernameAlreadyTaken()
{
// Arrange
var application = new SuperHeroWebApplicationFactory();
using (var scope = application.Services.CreateScope())
{
AuthenticationRequest request = new AuthenticationRequest();
request.Username = "eduardo";
request.Password = "password";

var client = application.CreateClient();

// Act
var response = await client.PostAsJsonAsync("/auth/signup", request);

// Assert
response.StatusCode.Should().Be(System.Net.HttpStatusCode.BadRequest);

var responseBody = await response.Content.ReadFromJsonAsync<ErrorResponse>();
responseBody.Should().NotBeNull();
responseBody!.Message.Should().Be("Username already taken.");
}
}
}
}
27 changes: 27 additions & 0 deletions IntegrationTests/DatabaseUtil.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using SuperHeroAPI.Data;
using SuperHeroAPI.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace IntegrationTests
{
internal class DatabaseUtil
{
private DatabaseUtil() { }

public static void Seed(DataContext context)
{
User user = new User
{
Username = "eduardo",
Password = BCrypt.Net.BCrypt.HashPassword("senha")
};
context.User.Add(user);

context.SaveChanges();
}
}
}
1 change: 1 addition & 0 deletions IntegrationTests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Xunit;
33 changes: 33 additions & 0 deletions IntegrationTests/IntegrationTests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.11" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.2.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\SuperHeroAPI\SuperHeroAPI.csproj" />
</ItemGroup>

</Project>
56 changes: 56 additions & 0 deletions IntegrationTests/SuperHeroWebApplicationFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using SuperHeroAPI.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace IntegrationTests
{
internal class SuperHeroWebApplicationFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
services.RemoveAll(typeof(DbContextOptions<DataContext>));
services.AddDbContext<DataContext>(options => {
options.UseSqlServer(getDatabaseUrl());
});
var dbContext = CreateDbContext(services);
dbContext.Database.EnsureDeleted();
dbContext.Database.EnsureCreated();
DatabaseUtil.Seed(dbContext);
});
}

private static string getDatabaseUrl()
{
var config = new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddJsonFile("testing.json", optional: false)
.Build();

var url = config["Database:URL"]!;

return url;
}

private static DataContext CreateDbContext(IServiceCollection services)
{
var serviceProvider = services.BuildServiceProvider();
var scope = serviceProvider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<DataContext>();

return dbContext;
}
}
}
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# SuperHeroAPI

> API inspired by [this video](https://youtu.be/8pH5Lv4d5-g)
> Swagger: http://localhost:5015/swagger
## Routes

| Description | Method | URL | Body |
|:---------------------------|:------:|:-------------------|:-------------------------------------------------------------------------------------------------------|
| Sign in | POST | /auth/signin | { "username": "username", "password": "password" } |
| Sign up | POST | /auth/signup | { "username": "username", "password": "password" } |
| Index user super heroes | GET | /superhero | |
| Show user super hero | GET | /superhero/:id | |
| Create user super hero | POST | /superhero | { "name": "string", "firstName": "string", "lastName": "string", "place": "string" } |
| Update user super hero | PUT | /superhero/:id | { "name": "string", "firstName": "string", "lastName": "string", "place": "string" } |
| Delete user super hero | DELETE | /superhero/:id | |

## Useful commands

1. If `dotnet-ef` is not installed: `dotnet tool install --global dotnet-ef`
0. Run migrations: `dotnet ef database update`
8 changes: 7 additions & 1 deletion SuperHero.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.34031.279
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SuperHeroAPI", "SuperHeroAPI\SuperHeroAPI.csproj", "{8FC7D42C-EDED-4733-B8AB-1B7628DAA2B9}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SuperHeroAPI", "SuperHeroAPI\SuperHeroAPI.csproj", "{8FC7D42C-EDED-4733-B8AB-1B7628DAA2B9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTests", "IntegrationTests\IntegrationTests.csproj", "{9F22F473-2621-441F-AE8B-8D8560098A7C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -15,6 +17,10 @@ Global
{8FC7D42C-EDED-4733-B8AB-1B7628DAA2B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8FC7D42C-EDED-4733-B8AB-1B7628DAA2B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8FC7D42C-EDED-4733-B8AB-1B7628DAA2B9}.Release|Any CPU.Build.0 = Release|Any CPU
{9F22F473-2621-441F-AE8B-8D8560098A7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9F22F473-2621-441F-AE8B-8D8560098A7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9F22F473-2621-441F-AE8B-8D8560098A7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F22F473-2621-441F-AE8B-8D8560098A7C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
4 changes: 2 additions & 2 deletions SuperHeroAPI/Controllers/AuthenticationController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public AuthenticationController(TokenService tokenService, IAuthenticationServic
}

[HttpPost("signin")]
public async Task<ActionResult<string>> signin([FromBody] SigninRequest request)
public async Task<ActionResult<string>> signin([FromBody] AuthenticationRequest request)
{
var user = await authenticationService.signin(request.Username, request.Password);
var token = tokenService.GenerateToken(user.Id);
Expand All @@ -28,7 +28,7 @@ public async Task<ActionResult<string>> signin([FromBody] SigninRequest request)
}

[HttpPost("signup")]
public async Task<ActionResult<string>> signup([FromBody] SigninRequest request)
public async Task<ActionResult<string>> signup([FromBody] AuthenticationRequest request)
{
var user = await authenticationService.signup(request.Username, request.Password);
var token = tokenService.GenerateToken(user.Id);
Expand Down
11 changes: 1 addition & 10 deletions SuperHeroAPI/Data/DataContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,8 @@ namespace SuperHeroAPI.Data
{
public class DataContext : DbContext
{
private readonly IConfiguration config;

public DataContext(DbContextOptions<DataContext> options, IConfiguration config) : base (options)
{
this.config = config;
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
public DataContext(DbContextOptions<DataContext> options) : base (options)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlServer(config["Database:URL"]);
}

public DbSet<SuperHero> SuperHeroes { get; set; }
Expand Down
8 changes: 8 additions & 0 deletions SuperHeroAPI/Models/Request/AuthenticationRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace SuperHeroAPI.Models.Request
{
public class AuthenticationRequest
{
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
}
}
8 changes: 0 additions & 8 deletions SuperHeroAPI/Models/Request/SigninRequest.cs

This file was deleted.

12 changes: 12 additions & 0 deletions SuperHeroAPI/Models/Response/ErrorResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace SuperHeroAPI.Models.Response
{
public class ErrorResponse
{
public string Message { get; set; }

public ErrorResponse(string message)
{
this.Message = message;
}
}
}
Loading

0 comments on commit 0041d3e

Please sign in to comment.