Skip to content

Commit

Permalink
Merge pull request #32 from jmg1138/end-to-end-tests
Browse files Browse the repository at this point in the history
End to end tests
  • Loading branch information
jmg1138 authored Dec 12, 2021
2 parents 04ea922 + 2275e5e commit 238da5f
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 11 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -458,3 +458,6 @@ $RECYCLE.BIN/

# Flyway
flyway*/

# Codecov.io
codecov
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# TrappyKeepy

[![codecov](https://codecov.io/gh/jmg1138/trappykeepy/branch/main/graph/badge.svg?token=ARrGqDcKhD)](https://codecov.io/gh/jmg1138/trappykeepy)
[![Build & Test](https://github.com/jmg1138/TrappyKeepy/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/jmg1138/TrappyKeepy/actions/workflows/build.yml) [![codecov](https://codecov.io/gh/jmg1138/trappykeepy/branch/main/graph/badge.svg?token=ARrGqDcKhD)](https://codecov.io/gh/jmg1138/trappykeepy)

A Simple Document Storage Web API

Expand All @@ -19,7 +19,7 @@ Endpoints are organized into CRUD operations by HTTP methods of `GET`, `POST`, `

Relationships are represented through nested endpoints. To `GET` all memberships of a specific group `id`, the format is `GET /v1/groups/{id}/memberships`.

Endpoints that retrieve many records will return simple objects. So, `GET /v1/users` will return an array of user objects with basic information for each user record, but no nested objects. Endpoints that retrieve a specific record may return complex objects. So, `GET /v1/users/{id}` will return a single user object including nested objects that may contain arrays of relational data such as the user's posted documents, group memberships, and document access permits.
Endpoints that retrieve many records will return simple objects. So, `GET /v1/users` will return an array of simple user objects with basic information for each user record, but no nested objects. Endpoints that retrieve a specific record may return complex objects. So, `GET /v1/users/{id}` will return a single complex user object including nested objects that may contain arrays of relational data such as the user's posted documents, group memberships, and document access permits.

You can [review the Swagger/OpenApi style documentation on SwaggerHub](https://app.swaggerhub.com/apis/nothingworksright/trappykeepy/v0.1.0).

Expand Down Expand Up @@ -97,7 +97,7 @@ curl --location --request POST 'https://api.trappykeepy.com/v1/groups' \

Standard Http response status codes are used in responses, such as 200 OK, 400 Bad Reqeust, 401 Unauthorized, and 500 Internal Server Error. When responding with a status 400 there will be helpful information included so that the client may make corrections and try again.

Responses are formatted as [JSend](https://github.com/omniti-labs/jsend). The JSON response will include a `status` key that will hold a value of success, fail, or error. Responses may also include key/values of `data` (the requested data), `message` (user-readable message), or `code` (a application code corresponding to the error, distinct from the Http status code).
Responses are formatted as [JSend](https://github.com/omniti-labs/jsend). The JSON response will include a `status` key that will hold a value of success, fail, or error. Responses may also include key/values of `data` (the requested data), `message` (user-readable message), or `code` (an application code corresponding to the error, distinct from the Http status code).

## Production

Expand Down Expand Up @@ -154,7 +154,7 @@ For development, a Vagrant box is setup to create a fresh PostgreSQL database in

Tests are written using xUnit.

When running `make test` it will run the command `dotnet test --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover`. This includes the end-to-end tests which require a live database to be present, and generates a code coverage report. Code coverage is uploaded to Codecov.io using their Uploader application.
When running `make test` it will run the command `dotnet test --verbosity quiet /p:CollectCoverage=true /p:CoverletOutputFormat=opencover`. This includes the end-to-end tests which require a live development database to be present, and generates a code coverage report. Code coverage is uploaded to Codecov.io manually using their Uploader application.

When tests run during the GitHub Action CI workflow the end-to-end tests are skipped because there is no PostgreSQL database present.

Expand Down
3 changes: 1 addition & 2 deletions TrappyKeepy.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Microsoft.OpenApi.Models;
using TrappyKeepy.Data;
using TrappyKeepy.Domain.Interfaces;
using TrappyKeepy.Domain.Maps;
using TrappyKeepy.Service;

var builder = WebApplication.CreateBuilder(args);
Expand Down Expand Up @@ -62,7 +61,7 @@
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.",
Description = "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer <token>'.",
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
Expand Down
58 changes: 58 additions & 0 deletions TrappyKeepy.Test/End2End/SessionEnd2EndTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.Text;
using Microsoft.AspNetCore.Mvc.Testing;
using System.Text.Json;
using System.Net.Http;
using System.Threading.Tasks;
using TrappyKeepy.Domain.Models;
using TrappyKeepy.Test.TestObjects;
using Xunit;

namespace TrappyKeepy.Test.End2End
{
public class SessionsEnd2EndTests : IClassFixture<WebApplicationFactory<Program>>
{
private SpawnyDb _db;
private readonly WebApplicationFactory<Program> _webApplicationFactory;
private DtoTestObjects _dto;
private JsonSerializerOptions _jsonOpts;

public SessionsEnd2EndTests(WebApplicationFactory<Program> webApplicationFactory)
{
_db = new SpawnyDb();
_webApplicationFactory = webApplicationFactory;
_dto = new DtoTestObjects();
_jsonOpts = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
}

[Fact]
[Trait("TestType", "End2End")]
public async Task PostSessionsWithValidCredentialsShouldCreateNewToken()
{
// ---------- ARRANGE ----------
await _db.RecycleDb();
var user = _dto.TestUserSessionDto;
var json = JsonSerializer.Serialize(user);
var content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage? response;

// ---------- ACT ----------
using (var client = _webApplicationFactory.CreateDefaultClient())
{
response = await client.PostAsync("/v1/sessions", content);
}

// ---------- ASSERT ----------
Assert.NotNull(response);
var responseJson = await response.Content.ReadAsStringAsync();
Assert.NotNull(responseJson);
var controllerResponse = JsonSerializer.Deserialize<ControllerResponse>(responseJson, _jsonOpts);
Assert.NotNull(controllerResponse);
Assert.NotNull(controllerResponse.Status);
Assert.Equal("success", controllerResponse.Status);
Assert.NotNull(controllerResponse.Data);
var token = controllerResponse.Data.ToString();
Assert.NotNull(token);
}
}
}
84 changes: 84 additions & 0 deletions TrappyKeepy.Test/End2End/SpawnyDb.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using Npgsql;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace TrappyKeepy.Test.End2End
{
public class SpawnyDb
{
private string _testDbName;

// Connection to use when creating the temporary testing database.
private NpgsqlConnection _connectionCreateTestDb;

// Connection to use when running queries in the temporary testing database.
private NpgsqlConnection _connectionUseTestDb;

public SpawnyDb()
{
_testDbName = "keepytest";

// Connect to the default/maintenance database named "postgres" when we create the temporary testing database.
_connectionCreateTestDb = new NpgsqlConnection("Host=localhost;Database=postgres;Port=15432;Username=dbowner;Password=dbpass;Pooling=false");

// Connect to the temporary testing database when we're ready to run queries like seeding test data.
_connectionUseTestDb = new NpgsqlConnection($"Host=localhost;Database={_testDbName};Port=15432;Username=dbowner;Password=dbpass;Pooling=false");

// Set the TKDB_CONN_STRING env var that the UnitOfWork class will use to connect to the database.
// This way when the WebApplicationFactory creates the API in memory for the e2e tests, the UnitOfWork
// class will connect to the temporary testing database instead of the development database.
Environment.SetEnvironmentVariable("TKDB_CONN_STRING", $"Host=localhost;Database={_testDbName};Port=15432;Username=dbowner;Password=dbpass;Pooling=false");
}

public async Task RecycleDb()
{
// Drop any old temporary testing database, and create a fresh one.
await _connectionCreateTestDb.OpenAsync();
await Drop();
await Create();
await _connectionCreateTestDb.CloseAsync();
await _connectionCreateTestDb.DisposeAsync();

// Seed test data into the temporary testing database for the next e2e test.
await _connectionUseTestDb.OpenAsync();
await SeedAdminUser();
await _connectionUseTestDb.CloseAsync();
await _connectionUseTestDb.DisposeAsync();
}

private async Task Drop()
{
using (var command = new NpgsqlCommand())
{
command.CommandText = $"DROP DATABASE IF EXISTS {_testDbName};";
command.Connection = _connectionCreateTestDb;
await command.PrepareAsync();
await command.ExecuteNonQueryAsync();
}
}

private async Task Create()
{
using (var command = new NpgsqlCommand())
{
command.CommandText = $"CREATE DATABASE {_testDbName} WITH TEMPLATE keepydb OWNER dbowner;";
command.Connection = _connectionCreateTestDb;
await command.PrepareAsync();
await command.ExecuteNonQueryAsync();
}
}

private async Task SeedAdminUser()
{
using (var command = new NpgsqlCommand())
{
command.CommandText = "SELECT * FROM tk.users_create('foo', 'passwordfoo', 'foo@trappykeepy.com', 'admin');";
command.Connection = _connectionUseTestDb;
await command.PrepareAsync();
await command.ExecuteReaderAsync();
}
}
}
}
43 changes: 40 additions & 3 deletions TrappyKeepy.Test/TestObjects/DomainTestObjects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,46 @@ public User TestUser
return new User()
{
Id = Guid.NewGuid(),
Name = "foo",
Password = "passwordfoo",
Email = "foo@trappykeepy.com",
Name = "basic",
Password = "passwordbasic",
Email = "basic@trappykeepy.com",
Role = "basic",
DateCreated = DateTime.Now.AddDays(-7),
DateActivated = DateTime.Now.AddDays(-6),
DateLastLogin = DateTime.Now.AddDays(-1)
};
}
}

public User TestManagerUser
{
get
{
return new User()
{
Id = Guid.NewGuid(),
Name = "manager",
Password = "passwordmanager",
Email = "manager@trappykeepy.com",
Role = "manager",
DateCreated = DateTime.Now.AddDays(-7),
DateActivated = DateTime.Now.AddDays(-6),
DateLastLogin = DateTime.Now.AddDays(-1)
};
}
}

public User TestAdminUser
{
get
{
return new User()
{
Id = Guid.NewGuid(),
Name = "admin",
Password = "passwordadmin",
Email = "admin@trappykeepy.com",
Role = "admin",
DateCreated = DateTime.Now.AddDays(-7),
DateActivated = DateTime.Now.AddDays(-6),
DateLastLogin = DateTime.Now.AddDays(-1)
Expand Down
3 changes: 2 additions & 1 deletion TrappyKeepy.Test/TrappyKeepy.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0-preview-20211130-02" />
<PackageReference Include="moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.2-pre.12" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
Expand Down
2 changes: 1 addition & 1 deletion Vagrant-setup/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ CREATE ROLE $APP_DB_USER
CREATE ROLE $APP_DB_OWNER
PASSWORD '$APP_DB_PASS'
NOSUPERUSER
NOCREATEDB
CREATEDB
CREATEROLE
NOINHERIT
LOGIN
Expand Down

0 comments on commit 238da5f

Please sign in to comment.