Initial Commit after switching from SVN to git

This commit is contained in:
2026-05-03 01:43:52 +02:00
parent ab8638e5bb
commit a4284234b2
910 changed files with 359931 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
using System;
using System.IO;
using programmersdigest.MT940Parser;
using programmersdigest.MT940Parser.Parsing;
using Xunit;
namespace Fuchs.Tests;
/// <summary>
/// MT940 BalanceParser robustness tests.
/// </summary>
public class BalanceParserTests
{
private readonly BalanceParser _parser = new();
[Fact]
public void ReadBalance_ValidCredit_ReturnsCorrectBalance()
{
// C230101EUR1000,00
var balance = _parser.ReadBalance("C230101EUR1000,00", BalanceType.Opening);
Assert.Equal(DebitCreditMark.Credit, balance.Mark);
Assert.Equal(new DateTime(2023, 1, 1), balance.Date);
Assert.Equal("EUR", balance.Currency);
Assert.Equal(1000.00m, balance.Amount);
Assert.Equal(BalanceType.Opening, balance.Type);
}
[Fact]
public void ReadBalance_ValidDebit_SetsDebitMark()
{
var balance = _parser.ReadBalance("D230601USD500,50", BalanceType.Closing);
Assert.Equal(DebitCreditMark.Debit, balance.Mark);
Assert.Equal("USD", balance.Currency);
Assert.Equal(500.50m, balance.Amount);
Assert.Equal(BalanceType.Closing, balance.Type);
}
[Fact]
public void ReadBalance_InvalidMark_ThrowsFormatException()
{
Assert.Throws<FormatException>(() => _parser.ReadBalance("X230101EUR1000,00", BalanceType.Opening));
}
[Fact]
public void ReadBalance_EmptyString_ThrowsInvalidDataException()
{
Assert.Throws<InvalidDataException>(() => _parser.ReadBalance("", BalanceType.Opening));
}
[Fact]
public void ReadBalance_TruncatedAfterMark_ThrowsInvalidDataException()
{
// Only "C" — no date follows
Assert.Throws<InvalidDataException>(() => _parser.ReadBalance("C", BalanceType.Opening));
}
[Fact]
public void ReadBalance_TruncatedAfterDate_ThrowsInvalidDataException()
{
// "C230101" — no currency
Assert.Throws<InvalidDataException>(() => _parser.ReadBalance("C230101", BalanceType.Opening));
}
[Fact]
public void ReadBalance_TruncatedAfterCurrency_ThrowsInvalidDataException()
{
// "C230101EUR" — no amount
Assert.Throws<InvalidDataException>(() => _parser.ReadBalance("C230101EUR", BalanceType.Opening));
}
[Fact]
public void ReadBalance_InvalidAmount_ThrowsFormatException()
{
Assert.Throws<FormatException>(() => _parser.ReadBalance("C230101EURabc", BalanceType.Opening));
}
[Fact]
public void ReadBalance_LowercaseCurrency_ThrowsInvalidDataException()
{
Assert.Throws<InvalidDataException>(() => _parser.ReadBalance("C230101eur1000,00", BalanceType.Opening));
}
[Fact]
public void ReadBalance_ZeroAmount_ParsesCorrectly()
{
var balance = _parser.ReadBalance("C230101EUR0,00", BalanceType.Opening);
Assert.Equal(0m, balance.Amount);
}
}
+127
View File
@@ -0,0 +1,127 @@
using System.Data;
using System.IO;
using System.Text;
using Fuchs.intranet;
using programmersdigest.MT940Parser;
using Xunit;
namespace Fuchs.Tests;
/// <summary>
/// Banking helper robustness tests.
/// </summary>
public class BankingDebitCreditMarkTests
{
[Theory]
[InlineData(DebitCreditMark.Credit, "C")]
[InlineData(DebitCreditMark.Debit, "D")]
[InlineData(DebitCreditMark.ReverseCredit, "RC")]
[InlineData(DebitCreditMark.ReverseDebit, "RD")]
public void DebitCreditMarkAbb_ReturnsExpected(DebitCreditMark mark, string expected)
{
Assert.Equal(expected, Banking.DebitCreditMarkAbb(mark));
}
[Fact]
public void DebitCreditMarkAbb_UndefinedValue_ReturnsEmpty()
{
Assert.Equal("", Banking.DebitCreditMarkAbb((DebitCreditMark)999));
}
}
public class BankingParseToDatatableTests
{
private static readonly string MinimalMT940 =
"\r\n:20:STARTUMSE\r\n" +
":25:DE12345678901234567890\r\n" +
":28C:00001/001\r\n" +
":60F:C230101EUR1000,00\r\n" +
":61:2301010101CR500,00NTRFREF123\r\n" +
":86:Gutschrift\r\n" +
":62F:C230101EUR1500,00\r\n" +
"-\r\n";
private static Stream ToStream(string content)
=> new MemoryStream(Encoding.UTF8.GetBytes(content));
[Fact]
public void ParseToDatatable_ValidMT940_ReturnsOneRow()
{
using var stream = ToStream(MinimalMT940);
var table = Banking.ParseToDatatable(stream);
Assert.Equal(1, table.Rows.Count);
}
[Fact]
public void ParseToDatatable_ValidMT940_HasAccountColumn()
{
using var stream = ToStream(MinimalMT940);
var table = Banking.ParseToDatatable(stream);
Assert.True(table.Columns.Contains("AccountIdentification"));
Assert.Equal("DE12345678901234567890", table.Rows[0]["AccountIdentification"]);
}
[Fact]
public void ParseToDatatable_ValidMT940_HasAmountColumn()
{
using var stream = ToStream(MinimalMT940);
var table = Banking.ParseToDatatable(stream);
Assert.True(table.Columns.Contains("Amount"));
Assert.Equal(500m, table.Rows[0]["Amount"]);
}
[Fact]
public void ParseToDatatable_ValidMT940_HasDebitCreditMark()
{
using var stream = ToStream(MinimalMT940);
var table = Banking.ParseToDatatable(stream);
Assert.Equal("C", table.Rows[0]["DebitCreditMark"]);
}
[Fact]
public void ParseToDatatable_EmptyStream_ReturnsEmptyTable()
{
using var stream = ToStream("");
var table = Banking.ParseToDatatable(stream);
Assert.Equal(0, table.Rows.Count);
}
[Fact]
public void ParseToDatatable_EmptyStream_HasDefaultSchema()
{
using var stream = ToStream("");
var table = Banking.ParseToDatatable(stream);
Assert.True(table.Columns.Contains("AccountIdentification"));
Assert.True(table.Columns.Contains("Amount"));
Assert.True(table.Columns.Contains("DebitCreditMark"));
}
[Fact]
public void ParseToDatatable_WithCustomSchema_UsesProvidedSchema()
{
var schema = new DataTable();
schema.Columns.Add("AccountIdentification", typeof(string));
schema.Columns.Add("Amount", typeof(decimal));
using var stream = ToStream(MinimalMT940);
var table = Banking.ParseToDatatable(stream, schemaDatatable: schema);
Assert.Equal(2, table.Columns.Count);
}
[Fact]
public void ParseToDatatable_MultipleStatements_ParsesAll()
{
var multi = MinimalMT940 + "\n" + MinimalMT940;
using var stream = ToStream(multi);
var table = Banking.ParseToDatatable(stream);
Assert.Equal(2, table.Rows.Count);
}
[Fact]
public void ParseToDatatable_MalformedContent_DoesNotThrow()
{
using var stream = ToStream("This is not MT940 data at all");
var ex = Record.Exception(() => Banking.ParseToDatatable(stream));
Assert.Null(ex);
}
}
+81
View File
@@ -0,0 +1,81 @@
using System;
using programmersdigest.MT940Parser.Parsing;
using Xunit;
namespace Fuchs.Tests;
/// <summary>
/// MT940 DateParser robustness tests.
/// </summary>
public class DateParserTests
{
[Fact]
public void Parse_ValidDate_ReturnsCorrectDateTime()
{
var result = DateParser.Parse("230115");
Assert.Equal(new DateTime(2023, 1, 15), result);
}
[Fact]
public void Parse_Year79_Returns2079()
{
// 79 is not > 79, so year += 2000
var result = DateParser.Parse("790601");
Assert.Equal(2079, result.Year);
}
[Fact]
public void Parse_Year80_Returns1980()
{
// 80 > 79 is true, so year += 1900
var result = DateParser.Parse("800101");
Assert.Equal(1980, result.Year);
}
[Fact]
public void Parse_Year00_Returns2000()
{
var result = DateParser.Parse("000315");
Assert.Equal(2000, result.Year);
}
[Fact]
public void Parse_NullInput_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() => DateParser.Parse(null!));
}
[Fact]
public void Parse_WrongLength_ThrowsFormatException()
{
Assert.Throws<FormatException>(() => DateParser.Parse("12345"));
}
[Fact]
public void Parse_TooLong_ThrowsFormatException()
{
Assert.Throws<FormatException>(() => DateParser.Parse("1234567"));
}
[Fact]
public void Parse_NonNumeric_ThrowsFormatException()
{
Assert.Throws<FormatException>(() => DateParser.Parse("23AB15"));
}
[Fact]
public void Parse_EmptyString_ThrowsFormatException()
{
Assert.Throws<FormatException>(() => DateParser.Parse(""));
}
[Theory]
[InlineData("231231", 2023, 12, 31)]
[InlineData("990101", 1999, 1, 1)]
[InlineData("010228", 2001, 2, 28)]
public void Parse_VariousDates_ReturnsExpected(string input, int year, int month, int day)
{
var result = DateParser.Parse(input);
Assert.Equal(new DateTime(year, month, day), result);
}
}
+113
View File
@@ -0,0 +1,113 @@
using System;
using MFR_RESTClient.generic;
using Xunit;
namespace Fuchs.Tests;
/// <summary>
/// MfrGeneric EntityHelper and TryConvert robustness tests.
/// </summary>
public class EntityHelperTests
{
[Fact]
public void EntityName_StandardType_ReturnsCorrectName()
{
Assert.Equal("Item", EntityHelper.EntityName(EntityTypes.Item));
}
[Fact]
public void EntityName_ServiceRequest_ReturnsUnmodified()
{
Assert.Equal("ServiceRequest", EntityHelper.EntityName(EntityTypes.ServiceRequest));
}
[Fact]
public void EntityValue_ValidName_ReturnsEnum()
{
Assert.Equal(EntityTypes.Invoice, EntityHelper.EntityValue("Invoice"));
}
[Fact]
public void EntityValue_CaseInsensitive_ReturnsEnum()
{
Assert.Equal(EntityTypes.Invoice, EntityHelper.EntityValue("invoice"));
}
[Fact]
public void EntityValue_UnknownName_ReturnsNone()
{
Assert.Equal(EntityTypes.none, EntityHelper.EntityValue("NonExistentEntity"));
}
[Fact]
public void EntityValue_EmptyString_ReturnsNone()
{
Assert.Equal(EntityTypes.none, EntityHelper.EntityValue(""));
}
[Theory]
[InlineData(EntityTypes.Item)]
[InlineData(EntityTypes.Invoice)]
[InlineData(EntityTypes.CostCenter)]
[InlineData(EntityTypes.ServiceObject)]
[InlineData(EntityTypes.StepListTemplate)]
public void EntityName_EntityValue_RoundTrip(EntityTypes et)
{
var name = EntityHelper.EntityName(et);
var result = EntityHelper.EntityValue(name);
Assert.Equal(et, result);
}
}
public class TryConvertTests
{
[Fact]
public void TryConvert_StringToInt_ReturnsCorrectValue()
{
var result = "42".TryConvert(typeof(int));
Assert.Equal(42, result);
}
[Fact]
public void TryConvert_StringToDouble_ReturnsCorrectValue()
{
var result = "3.14".TryConvert(typeof(double));
Assert.NotNull(result);
Assert.Equal(3.14, (double)result!, precision: 2);
}
[Fact]
public void TryConvert_StringToBool_ReturnsTrue()
{
var result = "True".TryConvert(typeof(bool));
Assert.Equal(true, result);
}
[Fact]
public void TryConvert_InvalidString_ThrowsArgumentException()
{
// TypeDescriptor.GetConverter throws ArgumentException for invalid int, not caught by TryConvert
Assert.ThrowsAny<Exception>(() => "not-a-number".TryConvert(typeof(int)));
}
[Fact]
public void TryConvert_EmptyString_ToInt_ThrowsException()
{
Assert.ThrowsAny<Exception>(() => "".TryConvert(typeof(int)));
}
[Fact]
public void TryConvert_StringToDateTime_ReturnsValue()
{
var result = "2023-01-15".TryConvert(typeof(DateTime));
Assert.NotNull(result);
Assert.IsType<DateTime>(result);
}
[Fact]
public void TryConvert_StringToDecimal_ReturnsValue()
{
var result = "99.99".TryConvert(typeof(decimal));
Assert.Equal(99.99m, result);
}
}
+32
View File
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Fuchs\Fuchs.csproj" />
<ProjectReference Include="..\Fuchs_DataService\Fuchs_DataService.csproj" />
<ProjectReference Include="..\MFR_RESTClient\MFR_RESTClient.csproj" />
<ProjectReference Include="..\..\..\WebProjectComponents\MT940Parser\MT940Parser\MT940Parser.csproj" />
</ItemGroup>
</Project>
+64
View File
@@ -0,0 +1,64 @@
using Fuchs.intranet;
using Xunit;
namespace Fuchs.Tests;
/// <summary>
/// Priority 4: Web application PDF helpers and data model logic.
/// </summary>
public class FuchsPdfParseDecTests
{
// ParseDec uses InvariantCulture: period is decimal separator, comma is group separator
[Theory]
[InlineData("1000.50", 1000.50)]
[InlineData("0", 0)]
[InlineData("123", 123)]
[InlineData("-99.99", -99.99)]
public void ParseDec_ValidInput_ReturnsTrueAndCorrectValue(string input, decimal expected)
{
var result = FuchsPdf.ParseDec(input, out decimal val);
Assert.True(result);
Assert.Equal(expected, val);
}
[Fact]
public void ParseDec_NullInput_ReturnsFalse()
{
var result = FuchsPdf.ParseDec(null, out decimal val);
Assert.False(result);
Assert.Equal(0m, val);
}
[Fact]
public void ParseDec_EmptyString_ReturnsFalse()
{
var result = FuchsPdf.ParseDec("", out decimal val);
Assert.False(result);
Assert.Equal(0m, val);
}
[Fact]
public void ParseDec_NonNumericString_ReturnsFalse()
{
var result = FuchsPdf.ParseDec("abc", out decimal val);
Assert.False(result);
Assert.Equal(0m, val);
}
[Fact]
public void ParseDec_DecimalStringInput_ReturnsTrueAndValue()
{
// Pass decimal as string to avoid ToString() culture dependency
var result = FuchsPdf.ParseDec("42.5", out decimal val);
Assert.True(result);
Assert.Equal(42.5m, val);
}
[Fact]
public void ParseDec_IntegerInput_ReturnsTrueAndValue()
{
var result = FuchsPdf.ParseDec(100, out decimal val);
Assert.True(result);
Assert.Equal(100m, val);
}
}
+94
View File
@@ -0,0 +1,94 @@
using MFR_RESTClient;
using Xunit;
namespace Fuchs.Tests;
/// <summary>
/// HtmlCleanUp.Clean robustness tests.
/// </summary>
public class HtmlCleanUpTests
{
[Fact]
public void Clean_PlainText_ReturnsSameText()
{
Assert.Equal("Hello World", HtmlCleanUp.Clean("Hello World"));
}
[Fact]
public void Clean_HtmlTags_AreStripped()
{
Assert.Equal("Bold text", HtmlCleanUp.Clean("<b>Bold</b> <i>text</i>"));
}
[Fact]
public void Clean_ParagraphTags_ConvertToNewlines()
{
var result = HtmlCleanUp.Clean("<p>Line1</p><p>Line2</p>");
Assert.Contains("Line1", result);
Assert.Contains("Line2", result);
Assert.Contains("\n", result);
}
[Fact]
public void Clean_BrTags_ConvertToNewlines()
{
var result = HtmlCleanUp.Clean("Line1<br>Line2<br/>Line3");
Assert.Contains("\n", result);
}
[Fact]
public void Clean_DivTags_ConvertToNewlines()
{
var result = HtmlCleanUp.Clean("<div>Block1</div><div>Block2</div>");
Assert.Contains("\n", result);
}
[Fact]
public void Clean_HtmlEntities_AreDecoded()
{
Assert.Equal("A & B", HtmlCleanUp.Clean("A &amp; B"));
}
[Fact]
public void Clean_MultipleNewlines_CollapsedToOne()
{
var result = HtmlCleanUp.Clean("<p></p><p></p><p>Text</p>");
// Should not have consecutive newlines
Assert.DoesNotContain("\n\n", result);
}
[Fact]
public void Clean_LeadingTrailingNewlines_AreRemoved()
{
var result = HtmlCleanUp.Clean("<p>Text</p>");
Assert.False(result.StartsWith("\n"));
Assert.False(result.EndsWith("\n"));
}
[Fact]
public void Clean_EmptyString_ReturnsEmpty()
{
Assert.Equal("", HtmlCleanUp.Clean(""));
}
[Fact]
public void Clean_NestedTags_StripsAll()
{
Assert.Equal("deep", HtmlCleanUp.Clean("<div><span><b>deep</b></span></div>"));
}
[Fact]
public void Clean_TagsWithAttributes_AreStripped()
{
Assert.Equal("link", HtmlCleanUp.Clean("<a href=\"https://example.com\" class=\"btn\">link</a>"));
}
[Theory]
[InlineData("&lt;script&gt;", "<script>")]
[InlineData("&quot;hello&quot;", "\"hello\"")]
[InlineData("&#39;test&#39;", "'test'")]
public void Clean_VariousEntities_AreDecoded(string input, string expected)
{
Assert.Equal(expected, HtmlCleanUp.Clean(input));
}
}
+98
View File
@@ -0,0 +1,98 @@
using System;
using System.Net;
using MFR_RESTClient;
using Xunit;
namespace Fuchs.Tests;
/// <summary>
/// Priority 3: REST client model construction and serialization.
/// </summary>
public class MFRClientConfigTests
{
[Fact]
public void Constructor_WithHttpUrl_PreservesScheme()
{
var config = new MFRClientConfig("http://example.com");
Assert.StartsWith("http://", config.BaseUrl);
}
[Fact]
public void Constructor_WithHttpsUrl_PreservesScheme()
{
var config = new MFRClientConfig("https://example.com");
Assert.StartsWith("https://", config.BaseUrl);
}
[Fact]
public void Constructor_WithoutScheme_PrependHttps()
{
var config = new MFRClientConfig("example.com");
Assert.StartsWith("https://", config.BaseUrl);
}
[Fact]
public void Constructor_BaseUrlAlwaysEndsWithSlash()
{
var config = new MFRClientConfig("https://example.com/odata");
Assert.EndsWith("/", config.BaseUrl);
}
[Fact]
public void Constructor_WithoutScheme_AppendsOdataPath()
{
var config = new MFRClientConfig("example.com");
Assert.Contains("/odata/", config.BaseUrl);
}
[Fact]
public void DefaultValues_AreCorrect()
{
var config = new MFRClientConfig("https://example.com/");
Assert.False(config.IsLogoutRequired);
Assert.False(config.IsSessionRequired);
Assert.Equal("", config.LoginAddress);
Assert.Equal("", config.LogoutAddress);
Assert.Equal("", config.TokenCookieName);
}
}
public class MFRClientCredentialsTests
{
[Fact]
public void Constructor_SetsUsernameAndPassword()
{
var creds = new MFRClientCredentials("admin", "secret");
Assert.Equal("admin", creds.Username);
Assert.Equal("secret", creds.Password);
}
[Fact]
public void Constructor_AllowsEmptyCredentials()
{
var creds = new MFRClientCredentials("", "");
Assert.Equal("", creds.Username);
Assert.Equal("", creds.Password);
}
}
public class MFRClientExceptionTests
{
[Fact]
public void Constructor_SetsAllProperties()
{
var ex = new MFRClientException(HttpStatusCode.Unauthorized, "Unauthorized", "Token expired");
Assert.Equal(HttpStatusCode.Unauthorized, ex.StatusCode);
Assert.Equal("Unauthorized", ex.Error);
Assert.Equal("Token expired", ex.Content);
Assert.Equal("Unauthorized", ex.Message);
}
[Fact]
public void Constructor_WithInnerException_ChainsProperly()
{
var inner = new InvalidOperationException("inner");
var ex = new MFRClientException(HttpStatusCode.InternalServerError, "Error", "body", inner);
Assert.Same(inner, ex.InnerException);
}
}
+101
View File
@@ -0,0 +1,101 @@
using System.IO;
using System.Linq;
using System.Text;
using programmersdigest.MT940Parser;
using Xunit;
namespace Fuchs.Tests;
/// <summary>
/// Priority 1: MT940 statement parsing correctness.
/// </summary>
public class MT940ParserTests
{
// Minimal valid MT940 fixture — parser requires CRLF and leading \r\n before :20:
private static readonly string MinimalMT940 =
"\r\n:20:STARTUMSE\r\n" +
":25:DE12345678901234567890\r\n" +
":28C:00001/001\r\n" +
":60F:C230101EUR1000,00\r\n" +
":61:2301010101CR500,00NTRFREF123\r\n" +
":86:Gutschrift\r\n" +
":62F:C230101EUR1500,00\r\n" +
"-\r\n";
private static Stream ToStream(string content)
=> new MemoryStream(Encoding.UTF8.GetBytes(content));
[Fact]
public void Parse_ValidMT940_ReturnsOneStatement()
{
using var parser = new Parser(ToStream(MinimalMT940));
var statements = parser.Parse().ToList();
Assert.Single(statements);
}
[Fact]
public void Parse_ValidMT940_HasCorrectAccountIdentification()
{
using var parser = new Parser(ToStream(MinimalMT940));
var statement = parser.Parse().First();
Assert.Equal("DE12345678901234567890", statement.AccountIdentification);
}
[Fact]
public void Parse_ValidMT940_HasCorrectTransactionReference()
{
using var parser = new Parser(ToStream(MinimalMT940));
var statement = parser.Parse().First();
Assert.Equal("STARTUMSE", statement.TransactionReferenceNumber);
}
[Fact]
public void Parse_ValidMT940_HasOneStatementLine()
{
using var parser = new Parser(ToStream(MinimalMT940));
var statement = parser.Parse().First();
Assert.Single(statement.Lines);
}
[Fact]
public void Parse_ValidMT940_OpeningBalanceCreditEUR()
{
using var parser = new Parser(ToStream(MinimalMT940));
var statement = parser.Parse().First();
Assert.Equal("EUR", statement.OpeningBalance.Currency);
Assert.Equal(DebitCreditMark.Credit, statement.OpeningBalance.Mark);
}
[Fact]
public void Parse_ValidMT940_ClosingBalanceAmount()
{
using var parser = new Parser(ToStream(MinimalMT940));
var statement = parser.Parse().First();
Assert.Equal(1500m, statement.ClosingBalance.Amount);
}
[Fact]
public void Parse_EmptyStream_ReturnsNoStatements()
{
using var parser = new Parser(ToStream(string.Empty));
var statements = parser.Parse().ToList();
Assert.Empty(statements);
}
[Fact]
public void Parse_MultipleStatements_ReturnsBoth()
{
var multiStatement = MinimalMT940 + "\n" + MinimalMT940;
using var parser = new Parser(ToStream(multiStatement));
var statements = parser.Parse().ToList();
Assert.Equal(2, statements.Count);
}
[Fact]
public void Dispose_DoesNotThrow()
{
var parser = new Parser(ToStream(MinimalMT940));
var exception = Record.Exception(() => parser.Dispose());
Assert.Null(exception);
}
}
+105
View File
@@ -0,0 +1,105 @@
using System;
using MFR_RESTClient;
using Newtonsoft.Json.Linq;
using Xunit;
namespace Fuchs.Tests;
/// <summary>
/// ODataEnvelope parsing robustness tests.
/// </summary>
public class ODataEnvelopeTests
{
[Fact]
public void Constructor_NullContent_DoesNotThrow()
{
var envelope = new ODataEnvelope(null);
Assert.Null(envelope.Value);
Assert.Null(envelope.Metadata);
Assert.Null(envelope.NextLink);
}
[Fact]
public void Constructor_EmptyContent_DoesNotThrow()
{
var envelope = new ODataEnvelope("");
Assert.Null(envelope.Value);
}
[Fact]
public void Constructor_WithValueArray_ParsesValue()
{
var json = """{"value": [{"id": 1}, {"id": 2}]}""";
var envelope = new ODataEnvelope(json);
Assert.NotNull(envelope.Value);
Assert.IsType<JArray>(envelope.Value);
Assert.Equal(2, ((JArray)envelope.Value).Count);
}
[Fact]
public void Constructor_WithMetadata_ParsesUri()
{
var json = """{"odata.metadata": "https://example.com/$metadata#Items", "value": []}""";
var envelope = new ODataEnvelope(json);
Assert.NotNull(envelope.Metadata);
Assert.Equal("https://example.com/$metadata#Items", envelope.Metadata!.ToString());
}
[Fact]
public void Constructor_WithNextLink_ParsesUri()
{
var json = """{"odata.nextLink": "https://example.com/odata/Items?$skip=50", "value": []}""";
var envelope = new ODataEnvelope(json);
Assert.NotNull(envelope.NextLink);
Assert.Contains("$skip=50", envelope.NextLink!.ToString());
}
[Fact]
public void Constructor_NonHttpMetadata_DoesNotSetMetadata()
{
var json = """{"odata.metadata": "not-a-url", "value": []}""";
var envelope = new ODataEnvelope(json);
Assert.Null(envelope.Metadata);
}
[Fact]
public void Constructor_NoValueKey_DeserializesWholeObject()
{
var json = """{"name": "test", "count": 5}""";
var envelope = new ODataEnvelope(json);
Assert.NotNull(envelope.Value);
}
[Fact]
public void Constructor_SetsUrlFromParameter()
{
var envelope = new ODataEnvelope("{}", url: "https://example.com/odata/Items");
Assert.Equal("https://example.com/odata/Items", envelope.Url);
}
[Fact]
public void ConvertToArray_SingleObject_WrapsInJArray()
{
var json = """{"name": "test"}""";
var envelope = new ODataEnvelope(json);
envelope.ConvertToArray();
Assert.IsType<JArray>(envelope.Value);
}
[Fact]
public void ConvertToArray_AlreadyJArray_RemainsJArray()
{
var json = """{"value": [{"id": 1}]}""";
var envelope = new ODataEnvelope(json);
envelope.ConvertToArray();
Assert.IsType<JArray>(envelope.Value);
}
[Fact]
public void ConvertToArray_NullValue_DoesNotThrow()
{
var envelope = new ODataEnvelope(null);
var ex = Record.Exception(() => envelope.ConvertToArray());
Assert.Null(ex);
}
}
+109
View File
@@ -0,0 +1,109 @@
using System;
using System.IO;
using programmersdigest.MT940Parser;
using programmersdigest.MT940Parser.Parsing;
using Xunit;
namespace Fuchs.Tests;
/// <summary>
/// MT940 StatementLineParser robustness tests.
/// </summary>
public class StatementLineParserTests
{
private readonly StatementLineParser _parser = new();
// Minimal valid: 6-digit valueDate + mark + amount + transType(1) + typeCode(3) + customerRef
// Example: 230101C500,00NTRFREF123
private const string ValidLine = "230101C500,00NTRFREF123";
[Fact]
public void ReadStatementLine_ValidCredit_ParsesCorrectly()
{
var line = _parser.ReadStatementLine(ValidLine);
Assert.Equal(new DateTime(2023, 1, 1), line.ValueDate);
Assert.Equal(DebitCreditMark.Credit, line.Mark);
Assert.Equal(500m, line.Amount);
Assert.Equal("TRF", line.TransactionTypeIdCode);
Assert.Equal("REF123", line.CustomerReference);
}
[Fact]
public void ReadStatementLine_DebitMark_SetsDebit()
{
var line = _parser.ReadStatementLine("230101D100,50NTRFPAY001");
Assert.Equal(DebitCreditMark.Debit, line.Mark);
Assert.Equal(100.50m, line.Amount);
}
[Fact]
public void ReadStatementLine_ReverseCreditMark_SetsReverseCredit()
{
var line = _parser.ReadStatementLine("230101RC100,00NTRFREF001");
Assert.Equal(DebitCreditMark.ReverseCredit, line.Mark);
}
[Fact]
public void ReadStatementLine_ReverseDebitMark_SetsReverseDebit()
{
var line = _parser.ReadStatementLine("230101RD100,00NTRFREF001");
Assert.Equal(DebitCreditMark.ReverseDebit, line.Mark);
}
[Fact]
public void ReadStatementLine_WithEntryDate_ParsesEntryDate()
{
// valueDate=230620, entryDate=0615 (4 digits), mark=C, amount=500,00, type=N, code=TRF, ref=REF123
// entryDate (June 15) <= valueDate (June 20) so no year correction
var line = _parser.ReadStatementLine("2306200615C500,00NTRFREF123");
Assert.Equal(new DateTime(2023, 6, 20), line.ValueDate);
Assert.Equal(new DateTime(2023, 6, 15), line.EntryDate);
}
[Fact]
public void ReadStatementLine_WithFundsCode_ParsesFundsCode()
{
// FundsCode is a single letter between mark and amount
var line = _parser.ReadStatementLine("230101DA500,00NTRFREF123");
Assert.Equal('A', line.FundsCode);
}
[Fact]
public void ReadStatementLine_EmptyString_ThrowsInvalidDataException()
{
Assert.Throws<InvalidDataException>(() => _parser.ReadStatementLine(""));
}
[Fact]
public void ReadStatementLine_TruncatedValueDate_ThrowsInvalidDataException()
{
Assert.Throws<InvalidDataException>(() => _parser.ReadStatementLine("2301"));
}
[Fact]
public void ReadStatementLine_InvalidTransactionType_ThrowsInvalidDataException()
{
// 'X' is not a valid transaction type (must be N, S, or F)
Assert.Throws<InvalidDataException>(() => _parser.ReadStatementLine("230101C500,00XTRFREF123"));
}
[Fact]
public void ReadStatementLine_InvalidMark_ThrowsFormatException()
{
Assert.Throws<FormatException>(() => _parser.ReadStatementLine("230101X500,00NTRFREF123"));
}
[Fact]
public void ReadStatementLine_TransactionTypeS_Accepted()
{
var line = _parser.ReadStatementLine("230101C500,00STRFREF123");
Assert.Equal("TRF", line.TransactionTypeIdCode);
}
[Fact]
public void ReadStatementLine_TransactionTypeF_Accepted()
{
var line = _parser.ReadStatementLine("230101C500,00FTRFREF123");
Assert.Equal("TRF", line.TransactionTypeIdCode);
}
}