diff --git a/Crypter.Test/Assert.cs b/Crypter.Test/Assert.cs
new file mode 100644
index 00000000..dd2e52e8
--- /dev/null
+++ b/Crypter.Test/Assert.cs
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2026 Crypter File Transfer
+ *
+ * This file is part of the Crypter file transfer project.
+ *
+ * Crypter is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The Crypter source code is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ * You can be released from the requirements of the aforementioned license
+ * by purchasing a commercial license. Buying such a license is mandatory
+ * as soon as you develop commercial activities involving the Crypter source
+ * code without disclosing the source code of your own applications.
+ *
+ * Contact the current copyright holder to discuss commercial license options.
+ */
+
+using System.Threading.Tasks;
+using EasyMonads;
+using NUnit.Framework;
+
+namespace Crypter.Test;
+
+///
+/// NUnit's assertions plus assertions for and .
+///
+///
+/// This type shadows for every test in the Crypter.Test namespace,
+/// so the inherited assertions are reached through the same Assert name they always were.
+/// The monad assertions return the matched value, which lets a test assert the state and bind the
+/// value in one statement.
+///
+internal abstract class Assert : NUnit.Framework.Assert
+{
+ ///
+ /// Assert that the maybe is in the Some state and return the value it holds.
+ ///
+ internal static T IsSome(Maybe maybe)
+ {
+ if (maybe.IsNone)
+ {
+ throw new AssertionException($"Expected Some, but was {Describe(maybe)}.");
+ }
+
+ return maybe.SomeOrDefault()!;
+ }
+
+ ///
+ /// Assert that the maybe is in the Some state and holds , then
+ /// return the value it holds.
+ ///
+ internal static T IsSome(Maybe maybe, T expectedValue)
+ {
+ T value = IsSome(maybe);
+ That(value, Is.EqualTo(expectedValue));
+ return value;
+ }
+
+ ///
+ /// Assert that the maybe is in the None state.
+ ///
+ internal static void IsNone(Maybe maybe)
+ {
+ if (maybe.IsSome)
+ {
+ Fail($"Expected None, but was {Describe(maybe)}.");
+ }
+ }
+
+ ///
+ /// Assert that the either is in the Right state and return the value it holds.
+ ///
+ internal static TRight IsRight(Either either)
+ {
+ if (!either.IsRight)
+ {
+ throw new AssertionException($"Expected Right, but was {Describe(either)}.");
+ }
+
+ return either.RightOrDefault(default!)!;
+ }
+
+ ///
+ /// Assert that the either is in the Right state and holds , then
+ /// return the value it holds.
+ ///
+ internal static TRight IsRight(Either either, TRight expectedValue)
+ {
+ TRight value = IsRight(either);
+ That(value, Is.EqualTo(expectedValue));
+ return value;
+ }
+
+ ///
+ /// Assert that the either is in the Left state and return the value it holds.
+ ///
+ internal static TLeft IsLeft(Either either)
+ {
+ if (!either.IsLeft)
+ {
+ throw new AssertionException($"Expected Left, but was {Describe(either)}.");
+ }
+
+ return either.LeftOrDefault(default!)!;
+ }
+
+ ///
+ /// Assert that the either is in the Left state and holds , then
+ /// return the value it holds.
+ ///
+ internal static TLeft IsLeft(Either either, TLeft expectedValue)
+ {
+ TLeft value = IsLeft(either);
+ That(value, Is.EqualTo(expectedValue));
+ return value;
+ }
+
+ ///
+ /// Assert that the either is in the Neither state.
+ ///
+ internal static void IsNeither(Either either)
+ {
+ if (!either.IsNeither)
+ {
+ Fail($"Expected Neither, but was {Describe(either)}.");
+ }
+ }
+
+ ///
+ /// Await the task, then assert that the maybe is in the Some state and return the value it holds.
+ ///
+ internal static async Task IsSomeAsync(Task> maybeTask)
+ {
+ return IsSome(await maybeTask);
+ }
+
+ ///
+ /// Await the task, then assert that the maybe is in the Some state and holds
+ /// , then return the value it holds.
+ ///
+ internal static async Task IsSomeAsync(Task> maybeTask, T expectedValue)
+ {
+ return IsSome(await maybeTask, expectedValue);
+ }
+
+ ///
+ /// Await the task, then assert that the maybe is in the None state.
+ ///
+ internal static async Task IsNoneAsync(Task> maybeTask)
+ {
+ IsNone(await maybeTask);
+ }
+
+ ///
+ /// Await the task, then assert that the either is in the Right state and return the value it holds.
+ ///
+ internal static async Task IsRightAsync(Task> eitherTask)
+ {
+ return IsRight(await eitherTask);
+ }
+
+ ///
+ /// Await the task, then assert that the either is in the Right state and holds
+ /// , then return the value it holds.
+ ///
+ internal static async Task IsRightAsync(Task> eitherTask, TRight expectedValue)
+ {
+ return IsRight(await eitherTask, expectedValue);
+ }
+
+ ///
+ /// Await the task, then assert that the either is in the Left state and return the value it holds.
+ ///
+ internal static async Task IsLeftAsync(Task> eitherTask)
+ {
+ return IsLeft(await eitherTask);
+ }
+
+ ///
+ /// Await the task, then assert that the either is in the Left state and holds
+ /// , then return the value it holds.
+ ///
+ internal static async Task IsLeftAsync(Task> eitherTask, TLeft expectedValue)
+ {
+ return IsLeft(await eitherTask, expectedValue);
+ }
+
+ ///
+ /// Await the task, then assert that the either is in the Neither state.
+ ///
+ internal static async Task IsNeitherAsync(Task> eitherTask)
+ {
+ IsNeither(await eitherTask);
+ }
+
+ private static string Describe(Maybe maybe)
+ {
+ return maybe.IsSome
+ ? $"Some({maybe.SomeOrDefault()})"
+ : "None";
+ }
+
+ private static string Describe(Either either)
+ {
+ if (either.IsRight)
+ {
+ return $"Right({either.RightOrDefault(default!)})";
+ }
+
+ return either.IsLeft
+ ? $"Left({either.LeftOrDefault(default!)})"
+ : "Neither";
+ }
+}
diff --git a/Crypter.Test/Common_Tests/Assert_Tests.cs b/Crypter.Test/Common_Tests/Assert_Tests.cs
new file mode 100644
index 00000000..fba26ee1
--- /dev/null
+++ b/Crypter.Test/Common_Tests/Assert_Tests.cs
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2026 Crypter File Transfer
+ *
+ * This file is part of the Crypter file transfer project.
+ *
+ * Crypter is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The Crypter source code is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ * You can be released from the requirements of the aforementioned license
+ * by purchasing a commercial license. Buying such a license is mandatory
+ * as soon as you develop commercial activities involving the Crypter source
+ * code without disclosing the source code of your own applications.
+ *
+ * Contact the current copyright holder to discuss commercial license options.
+ */
+
+using System.Threading.Tasks;
+using EasyMonads;
+using NUnit.Framework;
+
+namespace Crypter.Test.Common_Tests;
+
+///
+/// Every monad assertion is exercised twice: once against the state it expects, and once against a
+/// state it must reject. A helper that never fails would silently pass every test that depends on it.
+///
+///
+/// The rejection tests wrap the assertion in Assert.Throws rather than a try/catch. NUnit
+/// records a failed assertion against the running test as soon as it happens, and only
+/// Assert.Throws clears that record. A caught still fails the
+/// test.
+///
+[TestFixture]
+internal class Assert_Tests
+{
+ private const string SomeValue = "Frodo";
+ private const string OtherValue = "Bilbo";
+ private const int LeftValue = 42;
+ private const int OtherLeftValue = 24;
+
+ private static Maybe Some => Maybe.From(SomeValue);
+ private static Maybe None => Maybe.None;
+
+ private static Either Right => Either.FromRight(SomeValue);
+ private static Either Left => Either.FromLeft(LeftValue);
+ private static Either Neither => Either.Neither;
+
+ [Test]
+ public void Is_Some_Returns_The_Value()
+ {
+ string value = Assert.IsSome(Some);
+ Assert.That(value, Is.EqualTo(SomeValue));
+ }
+
+ [Test]
+ public void Is_Some_Fails_For_None()
+ {
+ AssertionException? exception = Assert.Throws(() => Assert.IsSome(None));
+ Assert.That(exception!.Message, Is.EqualTo("Expected Some, but was None."));
+ }
+
+ [Test]
+ public void Is_Some_With_An_Expected_Value_Returns_The_Value()
+ {
+ string value = Assert.IsSome(Some, SomeValue);
+ Assert.That(value, Is.EqualTo(SomeValue));
+ }
+
+ [Test]
+ public void Is_Some_With_An_Expected_Value_Fails_For_A_Different_Value()
+ {
+ Assert.Throws(() => Assert.IsSome(Some, OtherValue));
+ }
+
+ [Test]
+ public void Is_None_Passes_For_None()
+ {
+ Assert.IsNone(None);
+ }
+
+ [Test]
+ public void Is_None_Fails_For_Some()
+ {
+ AssertionException? exception = Assert.Throws(() => Assert.IsNone(Some));
+ Assert.That(exception!.Message, Is.EqualTo($"Expected None, but was Some({SomeValue})."));
+ }
+
+ [Test]
+ public void Is_Right_Returns_The_Value()
+ {
+ string value = Assert.IsRight(Right);
+ Assert.That(value, Is.EqualTo(SomeValue));
+ }
+
+ [Test]
+ public void Is_Right_Fails_For_Left()
+ {
+ AssertionException? exception = Assert.Throws(() => Assert.IsRight(Left));
+ Assert.That(exception!.Message, Is.EqualTo($"Expected Right, but was Left({LeftValue})."));
+ }
+
+ [Test]
+ public void Is_Right_Fails_For_Neither()
+ {
+ AssertionException? exception = Assert.Throws(() => Assert.IsRight(Neither));
+ Assert.That(exception!.Message, Is.EqualTo("Expected Right, but was Neither."));
+ }
+
+ [Test]
+ public void Is_Right_With_An_Expected_Value_Returns_The_Value()
+ {
+ string value = Assert.IsRight(Right, SomeValue);
+ Assert.That(value, Is.EqualTo(SomeValue));
+ }
+
+ [Test]
+ public void Is_Right_With_An_Expected_Value_Fails_For_A_Different_Value()
+ {
+ Assert.Throws(() => Assert.IsRight(Right, OtherValue));
+ }
+
+ [Test]
+ public void Is_Left_Returns_The_Value()
+ {
+ int value = Assert.IsLeft(Left);
+ Assert.That(value, Is.EqualTo(LeftValue));
+ }
+
+ [Test]
+ public void Is_Left_Fails_For_Right()
+ {
+ AssertionException? exception = Assert.Throws(() => Assert.IsLeft(Right));
+ Assert.That(exception!.Message, Is.EqualTo($"Expected Left, but was Right({SomeValue})."));
+ }
+
+ [Test]
+ public void Is_Left_Fails_For_Neither()
+ {
+ AssertionException? exception = Assert.Throws(() => Assert.IsLeft(Neither));
+ Assert.That(exception!.Message, Is.EqualTo("Expected Left, but was Neither."));
+ }
+
+ [Test]
+ public void Is_Left_With_An_Expected_Value_Returns_The_Value()
+ {
+ int value = Assert.IsLeft(Left, LeftValue);
+ Assert.That(value, Is.EqualTo(LeftValue));
+ }
+
+ [Test]
+ public void Is_Left_With_An_Expected_Value_Fails_For_A_Different_Value()
+ {
+ Assert.Throws(() => Assert.IsLeft(Left, OtherLeftValue));
+ }
+
+ [Test]
+ public void Is_Neither_Passes_For_Neither()
+ {
+ Assert.IsNeither(Neither);
+ }
+
+ [Test]
+ public void Is_Neither_Fails_For_Right()
+ {
+ AssertionException? exception = Assert.Throws(() => Assert.IsNeither(Right));
+ Assert.That(exception!.Message, Is.EqualTo($"Expected Neither, but was Right({SomeValue})."));
+ }
+
+ [Test]
+ public void Is_Some_Inside_A_Multiple_Scope_Does_Not_Return_A_Value()
+ {
+ bool returned = false;
+
+ Assert.Throws(() => Assert.Multiple(() =>
+ {
+ _ = Assert.IsSome(None);
+ returned = true;
+ }));
+
+ Assert.That(returned, Is.False);
+ }
+
+ [Test]
+ public void Is_Right_Inside_A_Multiple_Scope_Does_Not_Return_A_Value()
+ {
+ bool returned = false;
+
+ Assert.Throws(() => Assert.Multiple(() =>
+ {
+ _ = Assert.IsRight(Left);
+ returned = true;
+ }));
+
+ Assert.That(returned, Is.False);
+ }
+
+ [Test]
+ public void Is_Left_Inside_A_Multiple_Scope_Does_Not_Return_A_Value()
+ {
+ bool returned = false;
+
+ Assert.Throws(() => Assert.Multiple(() =>
+ {
+ _ = Assert.IsLeft(Right);
+ returned = true;
+ }));
+
+ Assert.That(returned, Is.False);
+ }
+
+ [Test]
+ public async Task Is_Some_Async_Returns_The_Value_Async()
+ {
+ string value = await Assert.IsSomeAsync(Task.FromResult(Some));
+ Assert.That(value, Is.EqualTo(SomeValue));
+ }
+
+ [Test]
+ public void Is_Some_Async_Fails_For_None()
+ {
+ AssertionException? exception =
+ Assert.ThrowsAsync(() => Assert.IsSomeAsync(Task.FromResult(None)));
+ Assert.That(exception!.Message, Is.EqualTo("Expected Some, but was None."));
+ }
+
+ [Test]
+ public async Task Is_Some_Async_With_An_Expected_Value_Returns_The_Value_Async()
+ {
+ string value = await Assert.IsSomeAsync(Task.FromResult(Some), SomeValue);
+ Assert.That(value, Is.EqualTo(SomeValue));
+ }
+
+ [Test]
+ public void Is_Some_Async_With_An_Expected_Value_Fails_For_A_Different_Value()
+ {
+ Assert.ThrowsAsync(() => Assert.IsSomeAsync(Task.FromResult(Some), OtherValue));
+ }
+
+ [Test]
+ public async Task Is_None_Async_Passes_For_None_Async()
+ {
+ await Assert.IsNoneAsync(Task.FromResult(None));
+ }
+
+ [Test]
+ public void Is_None_Async_Fails_For_Some()
+ {
+ AssertionException? exception =
+ Assert.ThrowsAsync(() => Assert.IsNoneAsync(Task.FromResult(Some)));
+ Assert.That(exception!.Message, Is.EqualTo($"Expected None, but was Some({SomeValue})."));
+ }
+
+ [Test]
+ public async Task Is_Right_Async_Returns_The_Value_Async()
+ {
+ string value = await Assert.IsRightAsync(Task.FromResult(Right));
+ Assert.That(value, Is.EqualTo(SomeValue));
+ }
+
+ [Test]
+ public void Is_Right_Async_Fails_For_Left()
+ {
+ AssertionException? exception =
+ Assert.ThrowsAsync(() => Assert.IsRightAsync(Task.FromResult(Left)));
+ Assert.That(exception!.Message, Is.EqualTo($"Expected Right, but was Left({LeftValue})."));
+ }
+
+ [Test]
+ public async Task Is_Right_Async_With_An_Expected_Value_Returns_The_Value_Async()
+ {
+ string value = await Assert.IsRightAsync(Task.FromResult(Right), SomeValue);
+ Assert.That(value, Is.EqualTo(SomeValue));
+ }
+
+ [Test]
+ public void Is_Right_Async_With_An_Expected_Value_Fails_For_A_Different_Value()
+ {
+ Assert.ThrowsAsync(() => Assert.IsRightAsync(Task.FromResult(Right), OtherValue));
+ }
+
+ [Test]
+ public async Task Is_Left_Async_Returns_The_Value_Async()
+ {
+ int value = await Assert.IsLeftAsync(Task.FromResult(Left));
+ Assert.That(value, Is.EqualTo(LeftValue));
+ }
+
+ [Test]
+ public void Is_Left_Async_Fails_For_Right()
+ {
+ AssertionException? exception =
+ Assert.ThrowsAsync(() => Assert.IsLeftAsync(Task.FromResult(Right)));
+ Assert.That(exception!.Message, Is.EqualTo($"Expected Left, but was Right({SomeValue})."));
+ }
+
+ [Test]
+ public async Task Is_Left_Async_With_An_Expected_Value_Returns_The_Value_Async()
+ {
+ int value = await Assert.IsLeftAsync(Task.FromResult(Left), LeftValue);
+ Assert.That(value, Is.EqualTo(LeftValue));
+ }
+
+ [Test]
+ public void Is_Left_Async_With_An_Expected_Value_Fails_For_A_Different_Value()
+ {
+ Assert.ThrowsAsync(() => Assert.IsLeftAsync(Task.FromResult(Left), OtherLeftValue));
+ }
+
+ [Test]
+ public async Task Is_Neither_Async_Passes_For_Neither_Async()
+ {
+ await Assert.IsNeitherAsync(Task.FromResult(Neither));
+ }
+
+ [Test]
+ public void Is_Neither_Async_Fails_For_Left()
+ {
+ AssertionException? exception =
+ Assert.ThrowsAsync(() => Assert.IsNeitherAsync(Task.FromResult(Left)));
+ Assert.That(exception!.Message, Is.EqualTo($"Expected Neither, but was Left({LeftValue})."));
+ }
+}