-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Setup bot status endpoint * Setup swagger docs
- Loading branch information
1 parent
f48a682
commit 3659feb
Showing
13 changed files
with
294 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace GuildWarsPartySearch.Tests.Infra; | ||
|
||
public sealed class TestLoggerWrapper<T> : ILogger<T> | ||
{ | ||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
|
||
public bool IsEnabled(LogLevel logLevel) | ||
{ | ||
return true; | ||
} | ||
|
||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) | ||
{ | ||
Console.WriteLine($"[{logLevel}] [{eventId}]\n{formatter(state, exception)}"); | ||
} | ||
} |
102 changes: 102 additions & 0 deletions
102
GuildWarsPartySearch.Tests/Services/BotStatus/BotStatusServiceTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
using FluentAssertions; | ||
using GuildWarsPartySearch.Server.Services.BotStatus; | ||
using GuildWarsPartySearch.Tests.Infra; | ||
using System.Net.WebSockets; | ||
|
||
namespace GuildWarsPartySearch.Tests.Services.BotStatus; | ||
|
||
[TestClass] | ||
public sealed class BotStatusServiceTests | ||
{ | ||
private readonly BotStatusService botStatusService; | ||
|
||
public BotStatusServiceTests() | ||
{ | ||
this.botStatusService = new BotStatusService(new TestLoggerWrapper<BotStatusService>()); | ||
} | ||
|
||
[TestMethod] | ||
public async Task AddBot_UniqueId_Succeeds() | ||
{ | ||
var result = await this.botStatusService.AddBot("uniqueId", new ClientWebSocket()); | ||
|
||
result.Should().BeTrue(); | ||
} | ||
|
||
[TestMethod] | ||
public async Task AddBot_DuplicateId_Fails() | ||
{ | ||
await this.botStatusService.AddBot("nonUniqueId", new ClientWebSocket()); | ||
var result = await this.botStatusService.AddBot("nonUniqueId", new ClientWebSocket()); | ||
|
||
result.Should().BeFalse(); | ||
} | ||
|
||
[TestMethod] | ||
public async Task AddBot_MultipleUniqueIds_Succeed() | ||
{ | ||
await this.botStatusService.AddBot("uniqueId1", new ClientWebSocket()); | ||
var result = await this.botStatusService.AddBot("uniqueId2", new ClientWebSocket()); | ||
|
||
result.Should().BeTrue(); | ||
} | ||
|
||
[TestMethod] | ||
[DataRow(null)] | ||
[DataRow("")] | ||
[DataRow(" ")] | ||
public async Task AddBot_NullOrEmptyId_Fails(string id) | ||
{ | ||
var result = await this.botStatusService.AddBot(id, new ClientWebSocket()); | ||
|
||
result.Should().BeFalse(); | ||
} | ||
|
||
[TestMethod] | ||
public async Task RemoveBot_ExistingId_Succeeds() | ||
{ | ||
await this.botStatusService.AddBot("uniqueId", new ClientWebSocket()); | ||
var result = await this.botStatusService.RemoveBot("uniqueId"); | ||
|
||
result.Should().BeTrue(); | ||
} | ||
|
||
[TestMethod] | ||
public async Task RemoveBot_NonExistingId_Fails() | ||
{ | ||
var result = await this.botStatusService.RemoveBot("uniqueId"); | ||
|
||
result.Should().BeFalse(); | ||
} | ||
|
||
[TestMethod] | ||
public async Task RemoveBot_MultipleRemoves_Fails() | ||
{ | ||
await this.botStatusService.AddBot("uniqueId", new ClientWebSocket()); | ||
await this.botStatusService.RemoveBot("uniqueId"); | ||
|
||
var result = await this.botStatusService.RemoveBot("uniqueId"); | ||
|
||
result.Should().BeFalse(); | ||
} | ||
|
||
[TestMethod] | ||
public async Task GetBots_NoBots_ReturnsNoBots() | ||
{ | ||
var result = await this.botStatusService.GetBots(); | ||
|
||
result.Should().BeEmpty(); | ||
} | ||
|
||
[TestMethod] | ||
public async Task GetBots_WithBots_ReturnsExpectedBots() | ||
{ | ||
await this.botStatusService.AddBot("uniqueId1", new ClientWebSocket()); | ||
await this.botStatusService.AddBot("uniqueId2", new ClientWebSocket()); | ||
|
||
var result = await this.botStatusService.GetBots(); | ||
|
||
result.Should().HaveCount(2); | ||
result.Should().BeEquivalentTo(["uniqueId1", "uniqueId2"]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
using GuildWarsPartySearch.Server.Filters; | ||
using GuildWarsPartySearch.Server.Services.BotStatus; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Swashbuckle.AspNetCore.Annotations; | ||
using System.Core.Extensions; | ||
|
||
namespace GuildWarsPartySearch.Server.Endpoints; | ||
|
||
[Route("status")] | ||
[ServiceFilter<ApiKeyProtected>] | ||
[ServiceFilter<RequireSsl>] | ||
public class StatusController : Controller | ||
{ | ||
private readonly IBotStatusService botStatusService; | ||
|
||
public StatusController( | ||
IBotStatusService botStatusService) | ||
{ | ||
this.botStatusService = botStatusService.ThrowIfNull(); | ||
} | ||
|
||
[HttpGet("bots")] | ||
[ProducesResponseType(200)] | ||
[ProducesResponseType(403)] | ||
[SwaggerOperation(Description = $"Protected by *{ApiKeyProtected.ApiKeyHeader}* header.\r\n\r\nRequires *SSL* protocol. (https://)")] | ||
public async Task<IActionResult> GetBotStatus([FromHeader(Name = ApiKeyProtected.ApiKeyHeader)] string _) | ||
{ | ||
return this.Ok(await this.botStatusService.GetBots()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using Microsoft.AspNetCore.Mvc.Filters; | ||
|
||
namespace GuildWarsPartySearch.Server.Filters; | ||
|
||
public sealed class RequireSsl : IActionFilter | ||
{ | ||
public void OnActionExecuted(ActionExecutedContext context) | ||
{ | ||
} | ||
|
||
public void OnActionExecuting(ActionExecutingContext context) | ||
{ | ||
if (context.HttpContext.Request.Scheme is not "https" or "wss") | ||
{ | ||
context.Result = new ForbiddenResponseActionResult($"This endpoint requires SSL communication"); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
64 changes: 64 additions & 0 deletions
64
GuildWarsPartySearch/Services/BotStatus/BotStatusService.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
using System.Collections.Concurrent; | ||
using System.Core.Extensions; | ||
using System.Extensions; | ||
using System.Net.WebSockets; | ||
|
||
namespace GuildWarsPartySearch.Server.Services.BotStatus; | ||
|
||
public sealed class BotStatusService : IBotStatusService | ||
{ | ||
private readonly ConcurrentDictionary<string, WebSocket> connectedBots = []; | ||
|
||
private readonly ILogger<BotStatusService> logger; | ||
|
||
public BotStatusService( | ||
ILogger<BotStatusService> logger) | ||
{ | ||
this.logger = logger.ThrowIfNull(); | ||
} | ||
|
||
public Task<bool> AddBot(string botId, WebSocket client) | ||
{ | ||
var scopedLogger = this.logger.CreateScopedLogger(nameof(this.AddBot), botId); | ||
if (botId.IsNullOrWhiteSpace()) | ||
{ | ||
scopedLogger.LogInformation("Unable to add bot. Null id"); | ||
return Task.FromResult(false); | ||
} | ||
|
||
if (!this.connectedBots.TryAdd(botId, client)) | ||
{ | ||
scopedLogger.LogInformation("Unable to add bot. Failed to add to cache"); | ||
return Task.FromResult(false); | ||
} | ||
|
||
scopedLogger.LogDebug("Added bot"); | ||
return Task.FromResult(true); | ||
} | ||
|
||
public Task<IEnumerable<string>> GetBots() | ||
{ | ||
var bots = this.connectedBots.Keys.AsEnumerable(); | ||
return Task.FromResult(bots); | ||
} | ||
|
||
public Task<bool> RemoveBot(string botId) | ||
{ | ||
var scopedLogger = this.logger.CreateScopedLogger(nameof(this.RemoveBot), botId); | ||
if (botId.IsNullOrEmpty()) | ||
{ | ||
scopedLogger.LogInformation("Unable to remove bot. Null id"); | ||
return Task.FromResult(false); | ||
} | ||
|
||
if (!this.connectedBots.TryRemove(botId, out var client) || | ||
client is null) | ||
{ | ||
scopedLogger.LogInformation("Unable to remove bot. Failed to remove bot from cache"); | ||
return Task.FromResult(false); | ||
} | ||
|
||
scopedLogger.LogDebug("Removed bot"); | ||
return Task.FromResult(true); | ||
} | ||
} |
Oops, something went wrong.