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
4 changes: 2 additions & 2 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
name: .NET Build and Test
on:
push:
branches: [main]
branches: [ main ]
pull_request:
branches: [main]
branches: [ main ]
permissions:
packages: write
jobs:
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/push_to_live.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
name: Push to Live
on:
workflow_run:
workflows: [.NET Build and Test]
types: [completed]
branches: [main]
workflows: [ .NET Build and Test ]
types: [ completed ]
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion Bottomly.ServiceDefaults/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ private static TBuilder AddOpenTelemetryExporters<TBuilder>(this TBuilder builde
if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
{
builder.Services.AddOpenTelemetry()
.UseAzureMonitor();
.UseAzureMonitor();
}

return builder;
Expand Down
24 changes: 12 additions & 12 deletions Bottomly.Tests/Bottomly.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,24 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Meziantou.Extensions.Logging.Xunit" Version="1.0.25" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.5" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="Shouldly" Version="4.3.0" />
<PackageReference Include="Testcontainers.MongoDb" Version="4.11.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
<PackageReference Include="coverlet.collector" Version="6.0.4"/>
<PackageReference Include="Meziantou.Extensions.Logging.Xunit" Version="1.0.25"/>
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.5"/>
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.3"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
<PackageReference Include="Moq" Version="4.20.72"/>
<PackageReference Include="Shouldly" Version="4.3.0"/>
<PackageReference Include="Testcontainers.MongoDb" Version="4.11.0"/>
<PackageReference Include="xunit" Version="2.9.3"/>
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4"/>
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
<Using Include="Xunit"/>
</ItemGroup>

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

</Project>
32 changes: 25 additions & 7 deletions Bottomly.Tests/Commands/AddKarmaCommandTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Bottomly.Commands;
using Bottomly.Models;
using Bottomly.Repositories;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Shouldly;

Expand All @@ -11,15 +12,16 @@ public class AddKarmaCommandTests
private readonly AddKarmaCommand _command;
private readonly Mock<IKarmaRepository> _mockRepo = new();

public AddKarmaCommandTests() => _command = new AddKarmaCommand(_mockRepo.Object);
public AddKarmaCommandTests() => _command = new AddKarmaCommand(_mockRepo.Object, NullLogger<AddKarmaCommand>.Instance);

[Fact]
public async Task ExecuteAsync_AwardsKarma_PersistsToRepository()
{
_mockRepo.Setup(r => r.AddAsync(It.IsAny<Karma>())).Returns(Task.CompletedTask);

await _command.ExecuteAsync("alice", "bob", "great job", KarmaType.PozzyPoz);
var result = await _command.ExecuteAsync("alice", "bob", "great job", KarmaType.PozzyPoz);

result.ShouldBeOfType<AddKarmaSuccessResult>();
_mockRepo.Verify(r => r.AddAsync(It.Is<Karma>(k =>
k.AwardedToUsername == "alice" &&
k.AwardedByUsername == "bob" &&
Expand All @@ -28,15 +30,31 @@ public async Task ExecuteAsync_AwardsKarma_PersistsToRepository()
}

[Fact]
public async Task ExecuteAsync_SelfPositiveKarma_ThrowsInvalidOperation() =>
await Should.ThrowAsync<InvalidOperationException>(() =>
_command.ExecuteAsync("alice", "alice", "", KarmaType.PozzyPoz));
public async Task ExecuteAsync_SelfPositiveKarma_ReturnsSelfAwardResult()
{
var result = await _command.ExecuteAsync("alice", "alice", "", KarmaType.PozzyPoz);

result.ShouldBeOfType<AddKarmaSelfAwardResult>();
}

[Fact]
public async Task ExecuteAsync_SelfNegativeKarma_DoesNotThrow()
public async Task ExecuteAsync_SelfNegativeKarma_ReturnsSuccessResult()
{
_mockRepo.Setup(r => r.AddAsync(It.IsAny<Karma>())).Returns(Task.CompletedTask);

await Should.NotThrowAsync(() => _command.ExecuteAsync("alice", "alice", "", KarmaType.NeggyNeg));
var result = await _command.ExecuteAsync("alice", "alice", "", KarmaType.NeggyNeg);

result.ShouldBeOfType<AddKarmaSuccessResult>();
}

[Fact]
public async Task ExecuteAsync_RepositoryThrows_ReturnsErrorResult()
{
_mockRepo.Setup(r => r.AddAsync(It.IsAny<Karma>())).ThrowsAsync(new Exception("DB error"));

var result = await _command.ExecuteAsync("alice", "bob", "great job", KarmaType.PozzyPoz);

var error = result.ShouldBeOfType<AddKarmaErrorResult>();
error.Error.ShouldBe("DB error");
}
}
11 changes: 7 additions & 4 deletions Bottomly.Tests/Commands/GiphyCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,25 @@ public async Task ExecuteAsync_EmptyInput_ReturnsBadInputResult()
[Fact]
public async Task ExecuteAsync_WithResult_ReturnsSuccessResult()
{
const string json = """{"data":{"url":"https://giphy.com/gifs/funny-cat"}}""";
const string json =
"""{"data":{"images":{"original":{"url":"https://media.giphy.com/media/funny-cat/giphy.gif"}}}}""";
var options = Options.Create(new BottomlyOptions { GiphyApiKey = "test" });
var command = new GiphyCommand(TestHelpers.CreateHttpClientFactory(json), options, NullLogger<GiphyCommand>.Instance);
var command = new GiphyCommand(TestHelpers.CreateHttpClientFactory(json), options,
NullLogger<GiphyCommand>.Instance);

var result = await command.ExecuteAsync("cat");

var successResult = result.ShouldBeOfType<GiphySuccessResult>();
successResult.Url.ShouldBe("https://giphy.com/gifs/funny-cat");
successResult.Url.ShouldBe("https://media.giphy.com/media/funny-cat/giphy.gif");
}

[Fact]
public async Task ExecuteAsync_EmptyDataArray_ReturnsEmptyResult()
{
const string json = """{"data":[]}""";
var options = Options.Create(new BottomlyOptions { GiphyApiKey = "test" });
var command = new GiphyCommand(TestHelpers.CreateHttpClientFactory(json), options, NullLogger<GiphyCommand>.Instance);
var command = new GiphyCommand(TestHelpers.CreateHttpClientFactory(json), options,
NullLogger<GiphyCommand>.Instance);

var result = await command.ExecuteAsync("obscuresearch");

Expand Down
6 changes: 2 additions & 4 deletions Bottomly.Tests/Commands/ImageSearchCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ public class ImageSearchCommandTests
Microsoft.Extensions.Options.Options.Create(new BottomlyOptions { BraveApiKey = "fake-key" });

private static ImageSearchCommand CreateCommand(string responseJson,
HttpStatusCode statusCode = HttpStatusCode.OK)
{
return new ImageSearchCommand(Options, TestHelpers.CreateHttpClientFactory(responseJson, statusCode),
HttpStatusCode statusCode = HttpStatusCode.OK) =>
new(Options, TestHelpers.CreateHttpClientFactory(responseJson, statusCode),
NullLogger<ImageSearchCommand>.Instance);
}

[Fact]
public async Task ExecuteAsync_EmptyInput_ReturnsEmptySearchTermErrorResult()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,8 @@ public class ImageSearchCommandIntegrationTests

private readonly ILogger<ImageSearchCommand> _logger;

public ImageSearchCommandIntegrationTests(ITestOutputHelper outputHelper)
{
public ImageSearchCommandIntegrationTests(ITestOutputHelper outputHelper) =>
_logger = XUnitLogger.CreateLogger<ImageSearchCommand>(outputHelper);
}

private static string? ApiKey => Configuration["bottomly_brave_api_key"];

Expand All @@ -47,11 +45,6 @@ private ImageSearchCommand CreateCommand()
}), factory, _logger);
}

private sealed class DefaultHttpClientFactory : IHttpClientFactory
{
public HttpClient CreateClient(string name) => new();
}

[Fact]
public async Task ExecuteAsync_EmptyInput_ReturnsEmptySearchTermErrorResult()
{
Expand Down Expand Up @@ -97,4 +90,9 @@ public async Task ExecuteAsync_KnownSearchTerm_ReturnsRelevantResult()
searchResult.Link.ShouldNotBeNullOrEmpty();
searchResult.Link.ShouldStartWith("http");
}

private sealed class DefaultHttpClientFactory : IHttpClientFactory
{
public HttpClient CreateClient(string name) => new();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,8 @@ public class SearchCommandIntegrationTests

private readonly ILogger<SearchCommand> _logger;

public SearchCommandIntegrationTests(ITestOutputHelper outputHelper)
{
public SearchCommandIntegrationTests(ITestOutputHelper outputHelper) =>
_logger = XUnitLogger.CreateLogger<SearchCommand>(outputHelper);
}

private static string? ApiKey => Configuration["bottomly_brave_api_key"];

Expand All @@ -47,11 +45,6 @@ private SearchCommand CreateCommand()
}), _logger, factory);
}

private sealed class DefaultHttpClientFactory : IHttpClientFactory
{
public HttpClient CreateClient(string name) => new();
}

[Fact]
public async Task ExecuteAsync_EmptyInput_ReturnsEmptySearchTermErrorResult()
{
Expand Down Expand Up @@ -96,4 +89,9 @@ public async Task ExecuteAsync_KnownSearchTerm_ReturnsRelevantResult()
var searchResult = (SearchResult)result;
searchResult.Link.ShouldContain("wikipedia");
}

private sealed class DefaultHttpClientFactory : IHttpClientFactory
{
public HttpClient CreateClient(string name) => new();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Bottomly.Commands;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Shouldly;

Expand All @@ -19,41 +20,41 @@ public WikipediaSearchCommandIntegrationTests()
var factory = new Mock<IHttpClientFactory>();
factory.Setup(f => f.CreateClient(It.IsAny<string>())).Returns(client);

_sut = new WikipediaSearchCommand(factory.Object);
_sut = new WikipediaSearchCommand(factory.Object, NullLogger<WikipediaSearchCommand>.Instance);
}

[Fact]
public async Task ExecuteAsync_EmptyInput_ReturnsNull()
public async Task ExecuteAsync_EmptyInput_ReturnsEmptyInputResult()
{
var result = await _sut.ExecuteAsync("");

result.ShouldBeNull();
result.ShouldBeOfType<WikipediaEmptyInputResult>();
}

[Fact]
public async Task ExecuteAsync_KnownSearchTerm_ReturnsResultWithWikipediaLink()
public async Task ExecuteAsync_KnownSearchTerm_ReturnsSuccessResultWithWikipediaLink()
{
var result = await _sut.ExecuteAsync("Albert Einstein");

result.ShouldNotBeNull();
result!.Text.ShouldNotBeNullOrEmpty();
result.Link.ShouldStartWith("https://en.wikipedia.org/wiki/");
var success = result.ShouldBeOfType<WikipediaSuccessResult>();
success.Text.ShouldNotBeNullOrEmpty();
success.Link.ShouldStartWith("https://en.wikipedia.org/wiki/");
}

[Fact]
public async Task ExecuteAsync_KnownSearchTerm_ReturnsExpectedTitle()
{
var result = await _sut.ExecuteAsync("London");

result.ShouldNotBeNull();
result!.Text.ShouldBe("London");
var success = result.ShouldBeOfType<WikipediaSuccessResult>();
success.Text.ShouldBe("London");
}

[Fact]
public async Task ExecuteAsync_GibberishInput_ReturnsNull()
public async Task ExecuteAsync_GibberishInput_ReturnsNotFoundResult()
{
var result = await _sut.ExecuteAsync("xyzzy_no_such_article_12345");

result.ShouldBeNull();
result.ShouldBeOfType<WikipediaNotFoundResult>();
}
}
26 changes: 13 additions & 13 deletions Bottomly.Tests/Commands/RegSearchCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ public async Task ExecuteAsync_SpecialChars_ReturnsSpecialCharsMessage()
public async Task ExecuteAsync_ValidReg_ParsesHtmlResponse()
{
const string html = """
<html><body>
<input id="VehicleColour" value="Blue" />
<input id="RegistrationYear" value="2019" />
<input id="VehicleMake" value="FORD" />
<input id="VehicleModel" value="FOCUS" />
<img id="searchResultCarImage" src="/images/car.jpg" />
</body></html>
""";
<html><body>
<input id="VehicleColour" value="Blue" />
<input id="RegistrationYear" value="2019" />
<input id="VehicleMake" value="FORD" />
<input id="VehicleModel" value="FOCUS" />
<img id="searchResultCarImage" src="/images/car.jpg" />
</body></html>
""";

var factory = TestHelpers.CreateHttpClientFactory(html);
var command = new RegSearchCommand(factory);
Expand Down Expand Up @@ -81,11 +81,11 @@ public async Task ExecuteAsync_HtmlWithError_ReturnsErrorText()
{
// An element with an empty value causes make[0] to throw, which triggers the catch block
const string html = """
<html><body>
<input id="VehicleMake" value="" />
<div class="ErrorMessage"><h3>Vehicle not found</h3></div>
</body></html>
""";
<html><body>
<input id="VehicleMake" value="" />
<div class="ErrorMessage"><h3>Vehicle not found</h3></div>
</body></html>
""";

var factory = TestHelpers.CreateHttpClientFactory(html);
var command = new RegSearchCommand(factory);
Expand Down
16 changes: 8 additions & 8 deletions Bottomly.Tests/Commands/SearchCommandTests.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System.Net;
using Bottomly.Commands.Search;
using Bottomly.Configuration;
using Bottomly.Tests.Helpers;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Shouldly;
using System.Net;

namespace Bottomly.Tests.Commands;

Expand Down Expand Up @@ -47,13 +47,13 @@ public async Task ExecuteAsync_WhitespaceInput_ReturnsEmptySearchTermErrorResult
public async Task ExecuteAsync_ApiReturnsResults_ReturnsSearchResultWithTitleAndLink()
{
const string json = """
{
"type": "search",
"web": {
"results": [{ "title": "DotNet", "url": "https://dotnet.microsoft.com" }]
}
}
""";
{
"type": "search",
"web": {
"results": [{ "title": "DotNet", "url": "https://dotnet.microsoft.com" }]
}
}
""";

var result = await CreateCommand(json).ExecuteAsync("dotnet");

Expand Down
Loading
Loading