Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
6e87f2f
Make SteamClient scoped so it's disposed after every job
3Mydlo3 Jan 6, 2023
5d00bd4
Add tests for scopes
3Mydlo3 Jan 6, 2023
1509ef0
Merge branch 'master' into bugfix/scopedSteamClient
3Mydlo3 Aug 3, 2023
5047773
Prevent attempts to connect to Steam multiple times
3Mydlo3 Aug 26, 2023
a7b0b3e
Merge branch 'bugfix/scopedSteamClient' into deployment
3Mydlo3 Aug 26, 2023
eb590df
Fix log message appearing even if client didn't connect
3Mydlo3 Aug 27, 2023
f5e8c98
Merge branch 'bugfix/scopedSteamClient' into deployment
3Mydlo3 Aug 27, 2023
a4263ef
Implement quick mods update to run before server startup
3Mydlo3 Nov 15, 2023
2974249
Fix incorrect media type error when making request without JSON body
3Mydlo3 Nov 15, 2023
26b3367
Merge branch 'feature/check-mods-on-server-startup' into deployment
3Mydlo3 Nov 15, 2023
5811752
Remove verifyMods from status
3Mydlo3 Nov 15, 2023
05ef869
Merge branch 'feature/check-mods-on-server-startup' into deployment
3Mydlo3 Nov 15, 2023
0d7de42
Prevent job if there was no nearest mission
3Mydlo3 Nov 15, 2023
ce2bc64
Merge branch 'bugfix/prevent-no-nearest-mission-found-errors' into de…
3Mydlo3 Nov 15, 2023
f1766fa
Perform mods verification during preparation for upcoming missions
3Mydlo3 Nov 15, 2023
f785125
Merge branch 'feature/check-mods-on-server-startup' into deployment
3Mydlo3 Nov 15, 2023
aeeb3e4
Add route and parameters logging to ApiKeyAttribute
3Mydlo3 Nov 15, 2023
2e335c2
Merge branch 'feature/log-executed-action-details' into deployment
3Mydlo3 Nov 15, 2023
73f95e7
Fix missing mods not returned as needing update
3Mydlo3 Nov 15, 2023
9b1474d
Merge branch 'feature/check-mods-on-server-startup' into deployment
3Mydlo3 Nov 15, 2023
2afc805
Merge remote-tracking branch 'origin/master' into deployment
3Mydlo3 Sep 14, 2024
313097f
Change to .NET 8
3Mydlo3 Sep 14, 2024
99b4a47
Update packages
3Mydlo3 Sep 14, 2024
008eded
Prevent single manifest download failure from abandoning whole update
3Mydlo3 Sep 14, 2024
10eb592
Catch the exception thrown on non existing item download
3Mydlo3 Sep 14, 2024
c1aa623
Drop Newtonsoft.Json (needs to wait for .NET 9)
3Mydlo3 Sep 14, 2024
539423c
Use cancellationToken when checking files
3Mydlo3 Sep 21, 2024
fab8509
Switch to .NET 9
3Mydlo3 Sep 21, 2024
41a2963
Switch libraries to RC1
3Mydlo3 Sep 21, 2024
59f1f4f
Merge branch 'maintenance/dotnet-9' into deployment-net9
3Mydlo3 Sep 21, 2024
4966110
Merge branch 'bugfix/handle-deleted-mods' into deployment-net9
3Mydlo3 Sep 21, 2024
125c190
Merge fix
3Mydlo3 Sep 21, 2024
b6c32cc
No errors in production code
3Mydlo3 Sep 21, 2024
bf475ab
All compiles
3Mydlo3 Sep 21, 2024
6a513e4
Fix error message in HttpClientBase
3Mydlo3 Sep 21, 2024
51d5289
WIP on error codes
3Mydlo3 Sep 21, 2024
6b8c95b
Merge branch 'master' into maintenance/use-strong-typed-errors
3Mydlo3 Nov 17, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ArmaForces.Arma.Server.Common.Errors;
using ArmaForces.Arma.Server.Config;
using ArmaForces.Arma.Server.Exceptions;
using ArmaForces.Arma.Server.Features.Keys;
Expand Down Expand Up @@ -42,7 +43,7 @@ public DedicatedServerTests()

_modsetConfigMock
.Setup(x => x.CopyConfigFiles())
.Returns(Result.Success);
.Returns(UnitResult.Success<IError>);
}

[Fact]
Expand Down Expand Up @@ -116,7 +117,7 @@ public void Start_ServerIsRunning_ThrowsServerRunningException()
var armaProcessMock = new Mock<IArmaProcess>();
armaProcessMock
.Setup(x => x.Start())
.Returns(Result.Success);
.Returns(UnitResult.Success<IError>());
armaProcessMock
.Setup(x => x.IsStopped)
.Returns(false);
Expand Down Expand Up @@ -151,7 +152,7 @@ private static Mock<IArmaProcess> CreateArmaProcessMock(bool isServer = false)

armaProcessMock
.Setup(x => x.Start())
.Returns(Result.Success);
.Returns(UnitResult.Success<IError>());
armaProcessMock
.Setup(x => x.IsStopped)
.Returns(true);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using CSharpFunctionalExtensions;
using ArmaForces.Arma.Server.Common.Errors;
using CSharpFunctionalExtensions;
using FluentAssertions;
using FluentAssertions.Execution;

namespace ArmaForces.Arma.Server.Tests.Helpers.Extensions
{
public static class ResultAssertionExtensions
{
public static void ShouldBeSuccess(this Result result)
public static void ShouldBeSuccess<TError>(this UnitResult<TError> result)
{
using (new AssertionScope())
{
Expand All @@ -19,12 +20,39 @@ public static void ShouldBeSuccess(this Result result)
}
}

public static void ShouldBeSuccess<T>(this Result<T> result)
public static void ShouldBeFailure<TError>(this UnitResult<TError> result, TError expectedError)
{
using (new AssertionScope())
{
result.IsFailure.Should().BeTrue();

if (result.IsFailure)
{
result.Error.Should().BeEquivalentTo(expectedError);
}
}
}

public static void ShouldBeFailure<TError>(this UnitResult<TError> result, string expectedErrorMessage)
where TError: IError
{
using (new AssertionScope())
{
result.IsFailure.Should().BeTrue();

if (result.IsFailure)
{
result.Error.Message.Should().BeEquivalentTo(expectedErrorMessage);
}
}
}

public static void ShouldBeSuccess<T, E>(this Result<T, E> result)
{
result.IsSuccess.Should().BeTrue();
}

public static void ShouldBeSuccess<T>(this Result<T> result, T expectedValue)
public static void ShouldBeSuccess<T, E>(this Result<T, E> result, T expectedValue)
{
using (new AssertionScope())
{
Expand All @@ -41,28 +69,29 @@ public static void ShouldBeSuccess<T>(this Result<T> result, T expectedValue)
}
}

public static void ShouldBeFailure(this Result result, string expectedErrorMessage)
public static void ShouldBeFailure<T, E>(this Result<T, E> result, string expectedErrorMessage)
where E : IError
{
using (new AssertionScope())
{
result.IsFailure.Should().BeTrue();

if (result.IsFailure)
{
result.Error.Should().BeEquivalentTo(expectedErrorMessage);
result.Error.Message.Should().BeEquivalentTo(expectedErrorMessage);
}
}
}

public static void ShouldBeFailure<T>(this Result<T> result, string expectedErrorMessage)
public static void ShouldBeFailure<T, E>(this Result<T, E> result, E expectedError)
{
using (new AssertionScope())
{
result.IsFailure.Should().BeTrue();

if (result.IsFailure)
{
result.Error.Should().BeEquivalentTo(expectedErrorMessage);
result.Error.Should().BeEquivalentTo(expectedError);
}
}
}
Expand Down
21 changes: 11 additions & 10 deletions ArmaForces.Arma.Server.Tests/Helpers/TestDedicatedServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using ArmaForces.Arma.Server.Common.Errors;
using ArmaForces.Arma.Server.Features.Modsets;
using ArmaForces.Arma.Server.Features.Processes;
using ArmaForces.Arma.Server.Features.Servers;
Expand Down Expand Up @@ -45,27 +46,27 @@ public async Task<ServerStatus> GetServerStatusAsync(CancellationToken cancellat
return new ServerStatus();
}

public Result Start()
public UnitResult<IError> Start()
{
if (!IsServerStopped) return Result.Failure("Server already running.");
if (!IsServerStopped) return new Error("Server already running.", TestErrorCode.ServerAlreadyRunning);

IsServerStopped = false;
return Result.Success();
return UnitResult.Success<IError>();
}

public async Task<Result> Shutdown()
public async Task<UnitResult<IError>> Shutdown()
{
if (IsServerStopped) return Result.Failure("Server not running.");
if (IsServerStopped) return new Error("Server not running.", ManagerErrorCode.ServerStopped);

IsServerStopped = true;
return Result.Success();
return UnitResult.Success<IError>();
}

public Result AddAndStartHeadlessClients(IEnumerable<IArmaProcess> headlessClients)
=> Result.Success();
public UnitResult<IError> AddAndStartHeadlessClients(IEnumerable<IArmaProcess> headlessClients)
=> UnitResult.Success<IError>();

public Task<Result> RemoveHeadlessClients(int headlessClientsToRemove)
=> Task.FromResult(Result.Success());
public Task<UnitResult<IError>> RemoveHeadlessClients(int headlessClientsToRemove)
=> Task.FromResult(UnitResult.Success<IError>());

public event Func<IDedicatedServer, Task> OnServerShutdown = null!;

Expand Down
15 changes: 15 additions & 0 deletions ArmaForces.Arma.Server.Tests/Helpers/TestErrorCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using ArmaForces.Arma.Server.Common.Errors;

namespace ArmaForces.Arma.Server.Tests.Helpers;

public class TestErrorCode : IErrorCode
{
public int? Value { get; init; }

public bool Is<T>(T value)
{
throw new System.NotImplementedException();
}

public static ErrorCode ServerAlreadyRunning => new();
}
49 changes: 49 additions & 0 deletions ArmaForces.Arma.Server/Common/Errors/Error.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.Net;
using CSharpFunctionalExtensions;

namespace ArmaForces.Arma.Server.Common.Errors;

/// <summary>
/// Represents details of an error.
/// </summary>
public record Error: IError
{
public string Message { get; init; }
public IErrorCode Code { get; init; }
public IError? InnerError { get; init; }

/// <summary>
/// Creates error instance.
/// </summary>
/// <param name="message">Message describing error details.</param>
/// <param name="code">Code of the error.</param>
/// <param name="innerError">Optional inner error.</param>
public Error(string message, IErrorCode code, IError? innerError = null)
{
Message = message;
Code = code;
InnerError = innerError;
}

public Error(string message, HttpStatusCode statusCode)
: this(message, new ErrorCode(statusCode)) { }

/// <summary>
///
/// </summary>
/// <param name="error"></param>
/// <returns></returns>
public static implicit operator UnitResult<IError>(Error error) => UnitResult.Failure<IError>(error);

/// <summary>
///
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public ICombine Combine(ICombine value)
{
throw new NotImplementedException();
}
}
34 changes: 34 additions & 0 deletions ArmaForces.Arma.Server/Common/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Net;

namespace ArmaForces.Arma.Server.Common.Errors;

public class ErrorCode : IErrorCode

Check warning on line 5 in ArmaForces.Arma.Server/Common/Errors/ErrorCode.cs

View workflow job for this annotation

GitHub Actions / tests

'ErrorCode' defines operator == or operator != but does not override Object.Equals(object o)

Check warning on line 5 in ArmaForces.Arma.Server/Common/Errors/ErrorCode.cs

View workflow job for this annotation

GitHub Actions / tests

'ErrorCode' defines operator == or operator != but does not override Object.GetHashCode()

Check warning on line 5 in ArmaForces.Arma.Server/Common/Errors/ErrorCode.cs

View workflow job for this annotation

GitHub Actions / tests

'ErrorCode' defines operator == or operator != but does not override Object.Equals(object o)

Check warning on line 5 in ArmaForces.Arma.Server/Common/Errors/ErrorCode.cs

View workflow job for this annotation

GitHub Actions / tests

'ErrorCode' defines operator == or operator != but does not override Object.GetHashCode()
{
public HttpStatusCode? HttpStatusCode { get; } = null;

public virtual int? Value => (int?) HttpStatusCode;

public ErrorCode()
{
}

public ErrorCode(HttpStatusCode httpStatusCode)
{
HttpStatusCode = httpStatusCode;
}

public bool Is<T>(T value) => value switch
{
HttpStatusCode httpStatusCode => httpStatusCode == HttpStatusCode,
int intValue => Value == intValue,
_ => false
};

public override string ToString() => HttpStatusCode.ToString() ?? "Unknown";

public static implicit operator ErrorCode(HttpStatusCode httpStatusCode) => new(httpStatusCode);

public static bool operator ==(ErrorCode errorCode, HttpStatusCode httpStatusCode) => errorCode.HttpStatusCode == httpStatusCode;

public static bool operator !=(ErrorCode errorCode, HttpStatusCode httpStatusCode) => !(errorCode == httpStatusCode);
}
19 changes: 19 additions & 0 deletions ArmaForces.Arma.Server/Common/Errors/IError.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using CSharpFunctionalExtensions;

namespace ArmaForces.Arma.Server.Common.Errors;

public interface IError : ICombine
{
public string Message { get; init; }

public IErrorCode Code { get; init; }

public IError? InnerError { get; init; }
}

public interface IErrorCode
{
public int? Value { get; }

public bool Is<T>(T value);
}
5 changes: 3 additions & 2 deletions ArmaForces.Arma.Server/Config/IConfig.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CSharpFunctionalExtensions;
using ArmaForces.Arma.Server.Common.Errors;
using CSharpFunctionalExtensions;

namespace ArmaForces.Arma.Server.Config {
public interface IConfig {
Expand Down Expand Up @@ -26,6 +27,6 @@ public interface IConfig {
/// Copies and fills config files.
/// </summary>
/// <returns>Result</returns>
Result CopyConfigFiles();
UnitResult<IError> CopyConfigFiles();
}
}
15 changes: 8 additions & 7 deletions ArmaForces.Arma.Server/Config/ModsetConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.IO;
using System.IO.Abstractions;
using System.Text.Json;
using ArmaForces.Arma.Server.Common.Errors;
using CSharpFunctionalExtensions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -46,7 +47,7 @@
SetProperties();
}

public Result CopyConfigFiles()
public UnitResult<IError> CopyConfigFiles()
{
return _serverConfig.CopyConfigFiles()
.Bind(GetOrCreateModsetConfigDir)
Expand All @@ -70,7 +71,7 @@
{
var modsetConfigModel =
JsonSerializer.Deserialize<ConfigSimpleModel>(_fileSystem.File.ReadAllText(ConfigJson));
if (modsetConfigModel.Server?.Password != null)

Check warning on line 74 in ArmaForces.Arma.Server/Config/ModsetConfig.cs

View workflow job for this annotation

GitHub Actions / tests

Dereference of a possibly null reference.
{
return modsetConfigModel.Server.Password;
}
Expand All @@ -82,14 +83,14 @@
JsonSerializer.Deserialize<ConfigSimpleModel>(
_fileSystem.File.ReadAllText(_serverConfig.ConfigJson));

return serverConfigModel.Server?.Password ?? string.Empty;

Check warning on line 86 in ArmaForces.Arma.Server/Config/ModsetConfig.cs

View workflow job for this annotation

GitHub Actions / tests

Dereference of a possibly null reference.
}

/// <summary>
/// Prepares config directory and files for current modset
/// </summary>
/// <returns>path to modsetConfig</returns>
private Result GetOrCreateModsetConfigDir()
private UnitResult<IError> GetOrCreateModsetConfigDir()
{
// Check for directory if present
if (!_fileSystem.Directory.Exists(DirectoryPath))
Expand All @@ -98,11 +99,11 @@
}

return _fileSystem.File.Exists(ConfigJson)
? Result.Success()
? UnitResult.Success<IError>()
: CreateConfigJson();
}

private Result CreateConfigJson()
private UnitResult<IError> CreateConfigJson()
{
// Set hostName according to pattern
var sampleServer = new Dictionary<string, string>();
Expand All @@ -116,13 +117,13 @@
var serializedJson = JsonSerializer.Serialize(sampleJSON, serializerOptions);
file.Write(serializedJson);
}
return Result.Success();
return UnitResult.Success<IError>();
}

/// <summary>
/// Prepares modset cfg files for server to load.
/// </summary>
private Result PrepareModsetConfig()
private UnitResult<IError> PrepareModsetConfig()
{
// Apply modset config on top of default config
var modsetConfig = new ConfigurationBuilder()
Expand All @@ -135,7 +136,7 @@
CreateConfigFiles(_serverConfig.BasicCfg, BasicCfg, modsetConfig);
CreateConfigFiles(_serverConfig.ServerCfg, ServerCfg, modsetConfig);

return Result.Success();
return UnitResult.Success<IError>();
}

private void CreateConfigFiles(
Expand Down
Loading
Loading