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
53 changes: 53 additions & 0 deletions TelegramMediaRelayBot.Tests/Domain/ErrorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using TelegramMediaRelayBot.Domain.Common;

namespace TelegramMediaRelayBot.Tests.Domain;

public class ErrorTests
{
[Fact]
public void Validation_ShouldCreateValidationError()
{
var error = Error.Validation("val.001", "invalid input");

Assert.Equal("val.001", error.Code);
Assert.Equal("invalid input", error.Message);
Assert.Equal(ErrorType.Validation, error.Type);
}

[Fact]
public void NotFound_ShouldCreateNotFoundError()
{
var error = Error.NotFound("nf.001", "resource missing");

Assert.Equal(ErrorType.NotFound, error.Type);
}

[Fact]
public void Infrastructure_ShouldCreateInfrastructureError()
{
var error = Error.Infrastructure("infra.001", "db down");

Assert.Equal(ErrorType.Infrastructure, error.Type);
Assert.Equal("infra.001", error.Code);
Assert.Equal("db down", error.Message);
}

[Fact]
public void EqualErrors_ShouldBeEqual()
{
var a = Error.Validation("code", "msg");
var b = Error.Validation("code", "msg");

Assert.Equal(a, b);
Assert.True(a == b);
}

[Fact]
public void DifferentErrors_ShouldNotBeEqual()
{
var a = Error.Validation("code", "msg");
var b = Error.NotFound("code", "msg");

Assert.NotEqual(a, b);
}
}
119 changes: 119 additions & 0 deletions TelegramMediaRelayBot.Tests/Domain/ResultTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using TelegramMediaRelayBot.Domain.Common;

namespace TelegramMediaRelayBot.Tests.Domain;

public class ResultTests
{
[Fact]
public void Success_ShouldSetValueAndIsSuccess()
{
var result = Result<int>.Success(42);

Assert.True(result.IsSuccess);
Assert.False(result.IsFailure);
Assert.Equal(42, result.Value);
Assert.Null(result.Error);
}

[Fact]
public void Failure_ShouldSetErrorAndIsFailure()
{
var error = Error.Validation("test", "fail");
var result = Result<int>.Failure(error);

Assert.True(result.IsFailure);
Assert.False(result.IsSuccess);
Assert.Equal(error, result.Error);
}

[Fact]
public void Map_OnSuccess_ShouldTransformValue()
{
var result = Result<int>.Success(10);

var mapped = result.Map(x => x * 2);

Assert.True(mapped.IsSuccess);
Assert.Equal(20, mapped.Value);
}

[Fact]
public void Map_OnFailure_ShouldPropagateError()
{
var error = Error.NotFound("nf", "not found");
var result = Result<int>.Failure(error);

var mapped = result.Map(x => x.ToString());

Assert.True(mapped.IsFailure);
Assert.Equal(error, mapped.Error);
}

[Fact]
public void Bind_OnSuccess_ShouldChainResults()
{
var result = Result<int>.Success(5);

var bound = result.Bind(x =>
x > 0 ? Result<string>.Success(x.ToString()) : Result<string>.Failure(Error.Validation("neg", "negative")));

Assert.True(bound.IsSuccess);
Assert.Equal("5", bound.Value);
}

[Fact]
public void Bind_OnFailure_ShouldPropagateError()
{
var error = Error.Infrastructure("err", "infra error");
var result = Result<int>.Failure(error);

var bound = result.Bind(x => Result<string>.Success(x.ToString()));

Assert.True(bound.IsFailure);
Assert.Equal(error, bound.Error);
}

[Fact]
public void Match_OnSuccess_ShouldCallOnSuccess()
{
var result = Result<int>.Success(7);

var output = result.Match(
onSuccess: v => $"ok:{v}",
onFailure: e => $"err:{e.Code}");

Assert.Equal("ok:7", output);
}

[Fact]
public void Match_OnFailure_ShouldCallOnFailure()
{
var error = Error.External("ext", "external");
var result = Result<string>.Failure(error);

var output = result.Match(
onSuccess: v => $"ok:{v}",
onFailure: e => $"err:{e.Code}");

Assert.Equal("err:ext", output);
}

[Fact]
public void StaticSuccess_ShouldCreateSuccessResult()
{
var result = Result.Success("hello");

Assert.True(result.IsSuccess);
Assert.Equal("hello", result.Value);
}

[Fact]
public void StaticFailure_ShouldCreateFailureResult()
{
var error = Error.Conflict("dup", "duplicate");
var result = Result.Failure<int>(error);

Assert.True(result.IsFailure);
Assert.Equal(error, result.Error);
}
}
106 changes: 106 additions & 0 deletions TelegramMediaRelayBot.Tests/Domain/UserSettingsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using TelegramMediaRelayBot.Domain.Models;

namespace TelegramMediaRelayBot.Tests.Domain;

public class UserSettingsTests
{
[Fact]
public void RoundTrip_ShouldPreserveValues()
{
var settings = new UserSettings();
settings.Distribution.DefaultAction = "send_to_all";
settings.Distribution.AutoSendDelaySeconds = 60;
settings.Privacy.InboxEnabled = true;

var json = settings.ToJson();
var restored = UserSettings.FromJson(json);

Assert.Equal("send_to_all", restored.Distribution.DefaultAction);
Assert.Equal(60, restored.Distribution.AutoSendDelaySeconds);
Assert.True(restored.Privacy.InboxEnabled);
}

[Fact]
public void DefaultValues_ShouldBeCorrect()
{
var settings = new UserSettings();

Assert.Equal("send_only_to_me", settings.Distribution.DefaultAction);
Assert.Equal("", settings.Distribution.DefaultActionCondition);
Assert.Equal(30, settings.Distribution.AutoSendDelaySeconds);
Assert.Empty(settings.Distribution.TargetGroupIds);
Assert.Empty(settings.Distribution.TargetContactIds);
Assert.False(settings.Privacy.InboxEnabled);
Assert.True(settings.Privacy.AllowContentForwarding);
Assert.Equal("everyone", settings.Privacy.WhoCanFindMe);
}

[Fact]
public void FromJson_EmptyString_ShouldReturnDefaults()
{
var settings = UserSettings.FromJson("");

Assert.Equal("send_only_to_me", settings.Distribution.DefaultAction);
Assert.Equal(30, settings.Distribution.AutoSendDelaySeconds);
}

[Fact]
public void FromJson_Null_ShouldReturnDefaults()
{
var settings = UserSettings.FromJson(null!);

Assert.NotNull(settings);
Assert.NotNull(settings.Distribution);
Assert.NotNull(settings.Privacy);
}

[Fact]
public void FromJson_InvalidJson_ShouldReturnDefaults()
{
var settings = UserSettings.FromJson("{broken json!!!");

Assert.NotNull(settings);
Assert.Equal("send_only_to_me", settings.Distribution.DefaultAction);
}

[Fact]
public void NestedSiteFilter_ShouldRoundTrip()
{
var settings = new UserSettings();
settings.Privacy.SiteFilter.Enabled = true;
settings.Privacy.SiteFilter.FilterType = "blocklist";
settings.Privacy.SiteFilter.BlockedDomains.Add("example.com");

var json = settings.ToJson();
var restored = UserSettings.FromJson(json);

Assert.True(restored.Privacy.SiteFilter.Enabled);
Assert.Equal("blocklist", restored.Privacy.SiteFilter.FilterType);
Assert.Single(restored.Privacy.SiteFilter.BlockedDomains);
Assert.Equal("example.com", restored.Privacy.SiteFilter.BlockedDomains[0]);
}

[Fact]
public void DistributionTargets_ShouldRoundTrip()
{
var settings = new UserSettings();
settings.Distribution.TargetGroupIds.AddRange([1, 2, 3]);
settings.Distribution.TargetContactIds.AddRange([10, 20]);

var json = settings.ToJson();
var restored = UserSettings.FromJson(json);

Assert.Equal([1, 2, 3], restored.Distribution.TargetGroupIds);
Assert.Equal([10, 20], restored.Distribution.TargetContactIds);
}

[Fact]
public void SiteFilterDefaults_ShouldBeCorrect()
{
var filter = new SiteFilterSettings();

Assert.False(filter.Enabled);
Assert.Equal("none", filter.FilterType);
Assert.Empty(filter.BlockedDomains);
}
}
86 changes: 86 additions & 0 deletions TelegramMediaRelayBot.Tests/Sessions/UserSessionManagerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using NSubstitute;

namespace TelegramMediaRelayBot.Tests.Sessions;

public class UserSessionManagerTests : IDisposable
{
private const long TestChatId = 999_000_001;

public UserSessionManagerTests()
{
// Clean up before each test to avoid shared state leaking
UserSessionManager.Remove(TestChatId);
UserSessionManager.Remove(TestChatId + 1);
UserSessionManager.Remove(TestChatId + 2);
}

public void Dispose()
{
UserSessionManager.Remove(TestChatId);
UserSessionManager.Remove(TestChatId + 1);
UserSessionManager.Remove(TestChatId + 2);
}

[Fact]
public void Set_And_Get_ShouldStoreAndRetrieveState()
{
var state = Substitute.For<IUserState>();
state.GetCurrentState().Returns("TestState");

UserSessionManager.Set(TestChatId, state);
var retrieved = UserSessionManager.Get(TestChatId);

Assert.NotNull(retrieved);
Assert.Equal("TestState", retrieved!.GetCurrentState());
}

[Fact]
public void Get_NonExistentKey_ShouldReturnNull()
{
var result = UserSessionManager.Get(TestChatId + 2);

Assert.Null(result);
}

[Fact]
public void ContainsKey_ShouldReturnTrueWhenExists()
{
var state = Substitute.For<IUserState>();
UserSessionManager.Set(TestChatId, state);

Assert.True(UserSessionManager.ContainsKey(TestChatId));
}

[Fact]
public void ContainsKey_ShouldReturnFalseWhenAbsent()
{
Assert.False(UserSessionManager.ContainsKey(TestChatId + 1));
}

[Fact]
public void Remove_ShouldDeleteSession()
{
var state = Substitute.For<IUserState>();
UserSessionManager.Set(TestChatId, state);

var removed = UserSessionManager.Remove(TestChatId);

Assert.True(removed);
Assert.False(UserSessionManager.ContainsKey(TestChatId));
Assert.Null(UserSessionManager.Get(TestChatId));
}

[Fact]
public void Remove_WithOutParam_ShouldReturnRemovedState()
{
var state = Substitute.For<IUserState>();
state.GetCurrentState().Returns("Removable");
UserSessionManager.Set(TestChatId, state);

var removed = UserSessionManager.Remove(TestChatId, out var removedState);

Assert.True(removed);
Assert.NotNull(removedState);
Assert.Equal("Removable", removedState!.GetCurrentState());
}
}
28 changes: 28 additions & 0 deletions TelegramMediaRelayBot.Tests/TelegramMediaRelayBot.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="xunit.v3" Version="1.1.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
</ItemGroup>

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

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

</Project>
Loading
Loading