Skip to content
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
20 changes: 9 additions & 11 deletions src/Elzik.Breef.Api/Auth/AuthExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using AspNetCore.Authentication.ApiKey;
using Microsoft.AspNetCore.Authorization;

namespace Elzik.Breef.Api.Auth;

Expand All @@ -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<AuthorizationOptions>(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();
});
}
}
9 changes: 8 additions & 1 deletion src/Elzik.Breef.Api/Elzik.Breef.Api.http
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions src/Elzik.Breef.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
{
options.AddDefaultPolicy(builder =>
{
builder.AllowAnyOrigin()

Check warning on line 52 in src/Elzik.Breef.Api/Program.cs

View workflow job for this annotation

GitHub Actions / build-ubuntu

Make sure this permissive CORS policy is safe here. (https://rules.sonarsource.com/csharp/RSPEC-5122)

Check warning on line 52 in src/Elzik.Breef.Api/Program.cs

View workflow job for this annotation

GitHub Actions / build-ubuntu

Make sure this permissive CORS policy is safe here. (https://rules.sonarsource.com/csharp/RSPEC-5122)

Check warning on line 52 in src/Elzik.Breef.Api/Program.cs

View workflow job for this annotation

GitHub Actions / build-ubuntu

Make sure this permissive CORS policy is safe here. (https://rules.sonarsource.com/csharp/RSPEC-5122)

Check warning on line 52 in src/Elzik.Breef.Api/Program.cs

View workflow job for this annotation

GitHub Actions / build-ubuntu

Make sure this permissive CORS policy is safe here. (https://rules.sonarsource.com/csharp/RSPEC-5122)
.AllowAnyMethod()
.AllowAnyHeader();
});
Expand Down Expand Up @@ -135,6 +135,9 @@
app.UseCors();
app.UseAuth();

app.MapGet("/health", () => Results.Ok(new { status = "Healthy" }))
.AllowAnonymous();

app.AddBreefEndpoints();

await app.RunAsync();
Expand Down
8 changes: 6 additions & 2 deletions tests/Elzik.Breef.Api.Tests.Functional/BreefTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}
5 changes: 4 additions & 1 deletion tests/Elzik.Breef.Api.Tests.Functional/BreefTestsDocker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
35 changes: 35 additions & 0 deletions tests/Elzik.Breef.Api.Tests.Functional/HealthEndpointTests.cs
Original file line number Diff line number Diff line change
@@ -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<Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory<Elzik.Breef.Api.Program>>
{
private readonly HttpClient _client;

public HealthEndpointTests(Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory<Elzik.Breef.Api.Program> 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<HealthResponse>();
body.ShouldNotBeNull();
body!.Status.ShouldBe("Healthy");
}

private class HealthResponse
{
public string Status { get; set; } = string.Empty;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down