diff --git a/.github/workflows/pre-merge.yml b/.github/workflows/pre-merge.yml
index 66066086..368af883 100644
--- a/.github/workflows/pre-merge.yml
+++ b/.github/workflows/pre-merge.yml
@@ -36,4 +36,11 @@ jobs:
dotnet test \
-e ASPNETCORE_ENVIRONMENT="${{ vars.ASPNETCORE_ENVIRONMENT }}" \
-e ConnectionStrings__AzureStorageAccount="${{ secrets.AZURE_STORAGE_ACCOUNT_CONNECTION_STRING }}" \
- -e ConnectionStrings__BiblioNexusDb="${{ secrets.BIBLIONEXUS_DB_CONNECTION_STRING }}"
+ -e ConnectionStrings__BiblioNexusDb="${{ secrets.BIBLIONEXUS_DB_CONNECTION_STRING }}" \
+ -e KeyVaultUri="${{ secrets.KEY_VAULT_URI }}" \
+ -e JwtSettings__Authority="${{ secrets.JWT_SETTINGS_AUTHORITY }}" \
+ -e JwtSettings__Audience="${{ secrets.JWT_SETTINGS_AUDIENCE }}" \
+ -e Auth0Settings__ApiClientId="${{ secrets.AUTH0SETTINGS_API_CLIENT_ID }}" \
+ -e Auth0Settings__ApplicationClientId="${{ secrets.AUTH0SETTINGS_APPLICATION_CLIENT_ID }}" \
+ -e Auth0Settings__Audience="${{ secrets.AUTH0_SETTINGS_AUDIENCE }}" \
+ -e Auth0Settings__BaseUri="${{ secrets.AUTH0_SETTINGS_BASE_URI }}"
diff --git a/Aquifer.sln b/Aquifer.sln
index 718eb2a1..e9139377 100644
--- a/Aquifer.sln
+++ b/Aquifer.sln
@@ -54,6 +54,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aquifer.Public.API.Integrat
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aquifer.Jobs.UnitTests", "tests\Aquifer.Jobs.UnitTests\Aquifer.Jobs.UnitTests.csproj", "{5205B15E-7521-40A7-8C66-19C231839E4F}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aquifer.API.IntegrationTests", "tests\Aquifer.API.IntegrationTests\Aquifer.API.IntegrationTests.csproj", "{EFCB9F65-BC0A-4AB6-9A42-19A0BD15ED30}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -118,6 +120,10 @@ Global
{5205B15E-7521-40A7-8C66-19C231839E4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5205B15E-7521-40A7-8C66-19C231839E4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5205B15E-7521-40A7-8C66-19C231839E4F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EFCB9F65-BC0A-4AB6-9A42-19A0BD15ED30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EFCB9F65-BC0A-4AB6-9A42-19A0BD15ED30}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EFCB9F65-BC0A-4AB6-9A42-19A0BD15ED30}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EFCB9F65-BC0A-4AB6-9A42-19A0BD15ED30}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -138,6 +144,7 @@ Global
{2F91DE0E-17AB-4DDD-9D73-9BE6CC1B1355} = {795E7203-D40F-4962-837D-DD39BAF2C930}
{0CF81CD4-6257-4A36-BF67-C1C581E93951} = {51DD9DE3-DA20-4C0B-82D6-029445F6BD42}
{5205B15E-7521-40A7-8C66-19C231839E4F} = {51DD9DE3-DA20-4C0B-82D6-029445F6BD42}
+ {EFCB9F65-BC0A-4AB6-9A42-19A0BD15ED30} = {51DD9DE3-DA20-4C0B-82D6-029445F6BD42}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8E32492B-EA66-40E4-A7A8-67E4ABA14A53}
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 5e9d3ba6..fd0437d6 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -17,6 +17,7 @@
+
diff --git a/src/Aquifer.API/Endpoints/BibleBooks/List/Endpoint.cs b/src/Aquifer.API/Endpoints/BibleBooks/List/Endpoint.cs
index a8af7b92..260578e6 100644
--- a/src/Aquifer.API/Endpoints/BibleBooks/List/Endpoint.cs
+++ b/src/Aquifer.API/Endpoints/BibleBooks/List/Endpoint.cs
@@ -4,7 +4,7 @@
namespace Aquifer.API.Endpoints.BibleBooks.List;
-public class Endpoint : EndpointWithoutRequest>
+public class Endpoint : EndpointWithoutRequest>
{
public override void Configure()
{
@@ -21,7 +21,9 @@ public override async Task HandleAsync(CancellationToken ct)
Id = (int)x.BookId,
Name = x.BookFullName,
Code = x.BookCode
- });
+ })
+ .ToList();
+
await SendOkAsync(response, ct);
}
}
\ No newline at end of file
diff --git a/src/Aquifer.API/Program.cs b/src/Aquifer.API/Program.cs
index 2f07d1e2..2ea3690c 100644
--- a/src/Aquifer.API/Program.cs
+++ b/src/Aquifer.API/Program.cs
@@ -83,4 +83,7 @@
app.UseResponseCachingVaryByAllQueryKeys();
app.MapEndpoints();
-app.Run();
\ No newline at end of file
+app.Run();
+
+// make this class public in order to access from integration tests
+public partial class Program;
\ No newline at end of file
diff --git a/tests/Aquifer.API.IntegrationTests/App.cs b/tests/Aquifer.API.IntegrationTests/App.cs
new file mode 100644
index 00000000..810aabf2
--- /dev/null
+++ b/tests/Aquifer.API.IntegrationTests/App.cs
@@ -0,0 +1,68 @@
+using FastEndpoints.Testing;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace Aquifer.API.IntegrationTests;
+
+///
+/// This is a FastEndpoints that sits on top of a
+/// which allows for in-memory web requests to be sent and received without actual network traffic.
+/// Details:
+/// * https://fast-endpoints.com/docs/integration-unit-testing
+/// * https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests
+///
+public sealed class App : AppFixture
+{
+ ///
+ /// The app is configured in before this method is called.
+ /// Only use this method to override or extend existing host configuration.
+ ///
+ protected override void ConfigureApp(IWebHostBuilder builder)
+ {
+ // The environment must be explicitly set as it will not default to "Development".
+ // On the build server this environment variable should be explicitly populated.
+ if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")))
+ {
+ builder.UseEnvironment(Environments.Development);
+ }
+ }
+
+ ///
+ /// The service registrations in run before this method is called.
+ /// Only use this method to add additional services or to override existing registrations from the API.
+ ///
+ protected override void ConfigureServices(IServiceCollection services)
+ {
+ }
+
+ ///
+ /// Configure Clients here.
+ /// The default Client is anonymous and has no API Key header value.
+ /// This is sufficient for anonymous Internal API tests because no actual web requests are sent via this fixture.
+ /// The API key is enforced by Azure API Management when proxying requests, and thus it is not needed locally.
+ /// Various authenticated client with different roles/permissions are also needed.
+ ///
+ ///
+ /// Example of adding a default header to the existing client:
+ ///
+ ///
+ /// Client.DefaultRequestHeaders.Add("api-key", "TODO");
+ ///
+ ///
+ /// An entirely new client could be defined in this class and set up in this method as well (e.g. an authenticated client).
+ ///
+ protected override Task SetupAsync()
+ {
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// Use this method to dispose of any s created in .
+ ///
+ protected override Task TearDownAsync()
+ {
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/tests/Aquifer.API.IntegrationTests/Aquifer.API.IntegrationTests.csproj b/tests/Aquifer.API.IntegrationTests/Aquifer.API.IntegrationTests.csproj
new file mode 100644
index 00000000..77be6022
--- /dev/null
+++ b/tests/Aquifer.API.IntegrationTests/Aquifer.API.IntegrationTests.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net9.0
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Aquifer.API.IntegrationTests/Endpoints/Bibles/Books/List/EndpointTests.cs b/tests/Aquifer.API.IntegrationTests/Endpoints/Bibles/Books/List/EndpointTests.cs
new file mode 100644
index 00000000..37a909aa
--- /dev/null
+++ b/tests/Aquifer.API.IntegrationTests/Endpoints/Bibles/Books/List/EndpointTests.cs
@@ -0,0 +1,19 @@
+using FastEndpoints;
+using System.Net;
+using Aquifer.API.Endpoints.BibleBooks.List;
+using FastEndpoints.Testing;
+
+namespace Aquifer.API.IntegrationTests.Endpoints.Bibles.Books.List;
+
+public sealed class EndpointTests(App _app) : TestBase
+{
+ [Fact]
+ public async Task ValidRequest_ShouldReturnSuccess()
+ {
+ var (response, result) = await _app.Client.GETAsync>();
+
+ response.StatusCode.Should().Be(HttpStatusCode.OK);
+ result.Should().HaveCountGreaterThan(80);
+ result.Should().Contain(r => r.Code == "4MA");
+ }
+}
\ No newline at end of file
diff --git a/tests/Aquifer.API.IntegrationTests/Endpoints/Resources/Content/Get/EndpointTests.cs b/tests/Aquifer.API.IntegrationTests/Endpoints/Resources/Content/Get/EndpointTests.cs
new file mode 100644
index 00000000..575bb11d
--- /dev/null
+++ b/tests/Aquifer.API.IntegrationTests/Endpoints/Resources/Content/Get/EndpointTests.cs
@@ -0,0 +1,21 @@
+using System.Net;
+using Aquifer.API.Endpoints.Resources.Content.Get;
+using FastEndpoints;
+using FastEndpoints.Testing;
+
+namespace Aquifer.API.IntegrationTests.Endpoints.Resources.Content.Get;
+
+public sealed class EndpointTests(App _app) : TestBase
+{
+ [Fact]
+ public async Task UnauthenticatedRequest_ShouldReturnUnauthorized()
+ {
+ var (response, _) = await _app.Client.GETAsync(
+ new Request
+ {
+ Id = 1890,
+ });
+
+ response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
+ }
+}
\ No newline at end of file
diff --git a/tests/Aquifer.Public.API.IntegrationTests/App.cs b/tests/Aquifer.Public.API.IntegrationTests/App.cs
index 4f804b24..05b38cb6 100644
--- a/tests/Aquifer.Public.API.IntegrationTests/App.cs
+++ b/tests/Aquifer.Public.API.IntegrationTests/App.cs
@@ -15,26 +15,6 @@ namespace Aquifer.Public.API.IntegrationTests;
///
public sealed class App : AppFixture
{
- ///
- /// Configure Clients here.
- /// The default Client is anonymous and has no API Key header value.
- /// This is sufficient for Public API tests because all routes are anonymous and no actual web requests are sent via this fixture.
- /// The API key is enforced by Azure API Management when proxying requests, and thus it is not needed locally.
- ///
- ///
- /// Example of adding a default header to the existing client:
- ///
- ///
- /// Client.DefaultRequestHeaders.Add("api-key", "TODO");
- ///
- ///
- /// An entirely new client could be defined in this class and set up in this method as well (e.g. an authenticated client).
- ///
- protected override Task SetupAsync()
- {
- return Task.CompletedTask;
- }
-
///
/// The app is configured in before this method is called.
/// Only use this method to override or extend existing host configuration.
@@ -57,6 +37,26 @@ protected override void ConfigureServices(IServiceCollection services)
{
}
+ ///
+ /// Configure Clients here.
+ /// The default Client is anonymous and has no API Key header value.
+ /// This is sufficient for Public API tests because all routes are anonymous and no actual web requests are sent via this fixture.
+ /// The API key is enforced by Azure API Management when proxying requests, and thus it is not needed locally.
+ ///
+ ///
+ /// Example of adding a default header to the existing client:
+ ///
+ ///
+ /// Client.DefaultRequestHeaders.Add("api-key", "TODO");
+ ///
+ ///
+ /// An entirely new client could be defined in this class and set up in this method as well (e.g. an authenticated client).
+ ///
+ protected override Task SetupAsync()
+ {
+ return Task.CompletedTask;
+ }
+
///
/// Use this method to dispose of any s created in .
///
diff --git a/tests/Aquifer.Public.API.IntegrationTests/Endpoints/Bibles/List/EndpointTests.cs b/tests/Aquifer.Public.API.IntegrationTests/Endpoints/Bibles/List/EndpointTests.cs
index 7e1402a7..e318644d 100644
--- a/tests/Aquifer.Public.API.IntegrationTests/Endpoints/Bibles/List/EndpointTests.cs
+++ b/tests/Aquifer.Public.API.IntegrationTests/Endpoints/Bibles/List/EndpointTests.cs
@@ -19,7 +19,8 @@ public async Task ValidRequest_ShouldReturnSuccess()
HasGreekAlignment = true,
});
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Contains(result, r => r.Abbreviation == "BSB");
+ response.StatusCode.Should().Be(HttpStatusCode.OK);
+
+ result.Should().Contain(r => r.Abbreviation == "BSB");
}
}
\ No newline at end of file
diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props
index 117cf812..34801561 100644
--- a/tests/Directory.Build.props
+++ b/tests/Directory.Build.props
@@ -9,12 +9,14 @@
+
+