diff --git a/src/Elzik.Breef.Api/Auth/AuthExtensions.cs b/src/Elzik.Breef.Api/Auth/AuthExtensions.cs index d3d09cc..bad1328 100644 --- a/src/Elzik.Breef.Api/Auth/AuthExtensions.cs +++ b/src/Elzik.Breef.Api/Auth/AuthExtensions.cs @@ -1,4 +1,5 @@ using AspNetCore.Authentication.ApiKey; +using Microsoft.AspNetCore.Authorization; namespace Elzik.Breef.Api.Auth; @@ -12,22 +13,19 @@ public static void AddAuth(this IServiceCollection services) options.KeyName = "BREEF-API-KEY"; options.Realm = "BreefAPI"; }); - services.AddAuthorization(); + + var authBuilder = services.AddAuthorizationBuilder(); + authBuilder.AddPolicy("RequireAuthenticated", p => p.RequireAuthenticatedUser()); + + services.Configure(options => + { + options.FallbackPolicy = options.GetPolicy("RequireAuthenticated"); + }); } public static void UseAuth(this WebApplication app) { app.UseAuthentication(); app.UseAuthorization(); - app.Use(async (context, next) => - { - if (context.User.Identity != null && !context.User.Identity.IsAuthenticated) - { - context.Response.StatusCode = 401; - await context.Response.WriteAsync("Unauthorised"); - return; - } - await next(); - }); } } diff --git a/src/Elzik.Breef.Api/Elzik.Breef.Api.http b/src/Elzik.Breef.Api/Elzik.Breef.Api.http index f7e122f..acc0b9f 100644 --- a/src/Elzik.Breef.Api/Elzik.Breef.Api.http +++ b/src/Elzik.Breef.Api/Elzik.Breef.Api.http @@ -1,8 +1,15 @@ @Elzik.Breef.Api_HostAddress = http://localhost:5079 +### Breefs + Post {{Elzik.Breef.Api_HostAddress}}/breefs Content-Type: application/json BREEF-API-KEY: test-key { - "url":"https://www.reddit.com/r/dotnet/comments/1o0j6or/im_giving_up_on_copilot_i_spend_more_time/" + "url":"https://www.reddit.com/r/selfhosted/comments/1og7kjd/a_note_to_myself_from_the_future_document/" } + +### Health + +Get {{Elzik.Breef.Api_HostAddress}}/health +Content-Type: application/json \ No newline at end of file diff --git a/src/Elzik.Breef.Api/Program.cs b/src/Elzik.Breef.Api/Program.cs index d867754..d575e88 100644 --- a/src/Elzik.Breef.Api/Program.cs +++ b/src/Elzik.Breef.Api/Program.cs @@ -135,6 +135,9 @@ public static async Task Main(string[] args) app.UseCors(); app.UseAuth(); + app.MapGet("/health", () => Results.Ok(new { status = "Healthy" })) + .AllowAnonymous(); + app.AddBreefEndpoints(); await app.RunAsync(); diff --git a/tests/Elzik.Breef.Api.Tests.Functional/BreefTestsBase.cs b/tests/Elzik.Breef.Api.Tests.Functional/BreefTestsBase.cs index a14e9c6..9513420 100644 --- a/tests/Elzik.Breef.Api.Tests.Functional/BreefTestsBase.cs +++ b/tests/Elzik.Breef.Api.Tests.Functional/BreefTestsBase.cs @@ -73,9 +73,13 @@ public async Task Unauthorised() // Assert response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized); + response.Headers.WwwAuthenticate.ShouldNotBeEmpty(); + var challenge = response.Headers.WwwAuthenticate.First(); + challenge.Scheme.ShouldBe("ApiKey"); + challenge.Parameter.ShouldNotBeNullOrEmpty(); + challenge.Parameter.ShouldContain("BREEF-API-KEY"); var responseString = await response.Content.ReadAsStringAsync(); - responseString.ShouldNotBeNullOrEmpty(); - responseString.ShouldContain("Unauthorised"); + responseString.ShouldBeEmpty(); } } } \ No newline at end of file diff --git a/tests/Elzik.Breef.Api.Tests.Functional/BreefTestsDocker.cs b/tests/Elzik.Breef.Api.Tests.Functional/BreefTestsDocker.cs index 3fd80d0..55818bc 100644 --- a/tests/Elzik.Breef.Api.Tests.Functional/BreefTestsDocker.cs +++ b/tests/Elzik.Breef.Api.Tests.Functional/BreefTestsDocker.cs @@ -80,7 +80,10 @@ public BreefTestsDocker(ITestOutputHelper testOutputHelper) .WithEnvironment("breef_Wallabag__Password", breefWallabagPassword) .WithEnvironment("breef_Wallabag__ClientId", breefWallabagClientId) .WithEnvironment("breef_Wallabag__ClientSecret", breefWallabagClientSecret) - .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(8080)) + .WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(request => request + .ForPort(8080) + .ForPath("/health") + .ForStatusCode(System.Net.HttpStatusCode.OK))) .WithOutputConsumer(outputConsumer) .Build(); } diff --git a/tests/Elzik.Breef.Api.Tests.Functional/HealthEndpointTests.cs b/tests/Elzik.Breef.Api.Tests.Functional/HealthEndpointTests.cs new file mode 100644 index 0000000..593990e --- /dev/null +++ b/tests/Elzik.Breef.Api.Tests.Functional/HealthEndpointTests.cs @@ -0,0 +1,35 @@ +using Shouldly; +using System.Net; +using System.Net.Http.Json; +using Xunit; + +namespace Elzik.Breef.Api.Tests.Functional +{ + public class HealthEndpointTests : IClassFixture> + { + private readonly HttpClient _client; + + public HealthEndpointTests(Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory factory) + { + _client = factory.CreateClient(); + } + + [Fact] + public async Task Health_Called_ReturnsOK() + { + // Act + var response = await _client.GetAsync("/health"); + + // Assert + response.StatusCode.ShouldBe(HttpStatusCode.OK); + var body = await response.Content.ReadFromJsonAsync(); + body.ShouldNotBeNull(); + body!.Status.ShouldBe("Healthy"); + } + + private class HealthResponse + { + public string Status { get; set; } = string.Empty; + } + } +} diff --git a/tests/Elzik.Breef.Infrastructure.Tests.Unit/ContentExtractors/Reddit/SubRedditExtractorTests.cs b/tests/Elzik.Breef.Infrastructure.Tests.Unit/ContentExtractors/Reddit/SubRedditExtractorTests.cs index 80c4b2a..3b6f49e 100644 --- a/tests/Elzik.Breef.Infrastructure.Tests.Unit/ContentExtractors/Reddit/SubRedditExtractorTests.cs +++ b/tests/Elzik.Breef.Infrastructure.Tests.Unit/ContentExtractors/Reddit/SubRedditExtractorTests.cs @@ -282,29 +282,6 @@ public async Task GetSubredditImageUrlAsync_ImageKeyExistsAndIsAccessible_Return result.ShouldBe(imageUrl); } - [Theory] - [InlineData("programming")] - [InlineData("learnprogramming")] - [InlineData("AskReddit")] - [InlineData("funny")] - public async Task GetSubredditImageUrlAsync_ValidSubredditName_CallsCorrectAboutUrl(string subredditName) - { - // Arrange - var expectedUrl = $"https://www.reddit.com/r/{subredditName}/about.json"; - var json = JsonSerializer.Serialize(new { data = new { } }); - - var mockHandler = new MockHttpMessageHandler(json, System.Net.HttpStatusCode.OK); - var httpClient = new HttpClient(mockHandler); - _mockHttpClientFactory.CreateClient("BreefDownloader").Returns(httpClient); - - // Act - await _extractor.GetSubredditImageUrlAsync(subredditName); - - // Assert - // Since we're using MockHttpMessageHandler, we can't easily verify the exact URL called - // The test passes if no exception is thrown and the method completes successfully - } - [Fact] public async Task GetSubredditImageUrlAsync_NoImageKeysExist_ReturnsFallbackImageUrl() {