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
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@
</PackageReference>

<!-- Meziantou - Comprehensive code quality -->
<PackageReference Include="Meziantou.Analyzer" Version="3.0.125">
<PackageReference Include="Meziantou.Analyzer" Version="3.0.139">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

<!-- SonarAnalyzer - Industry-standard analysis -->
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.29.0.143774">
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.31.0.145097">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.10" />
<PackageReference Include="Wolfgang.Etl.TestKit" Version="0.10.0" />
<PackageReference Include="Wolfgang.Etl.TestKit" Version="0.14.0" />
</ItemGroup>

<!-- Analyzer PackageReferences are centralized in Directory.Build.props -->
Expand Down
2 changes: 1 addition & 1 deletion src/Wolfgang.Etl.Xml/Wolfgang.Etl.Xml.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Wolfgang.Etl.Abstractions" Version="0.20.0" />
<PackageReference Include="Wolfgang.Etl.Abstractions" Version="0.21.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.10" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.10" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" Condition="!$(TargetFramework.StartsWith('net5.')) AND !$(TargetFramework.StartsWith('net6.')) AND !$(TargetFramework.StartsWith('net7.')) AND !$(TargetFramework.StartsWith('netcoreapp'))" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" Condition="$(TargetFramework.StartsWith('net5.')) OR $(TargetFramework.StartsWith('net6.')) OR $(TargetFramework.StartsWith('net7.')) OR $(TargetFramework.StartsWith('netcoreapp'))" />
<PackageReference Include="System.Linq.Async" Version="7.0.1" />
<PackageReference Include="Wolfgang.Etl.TestKit" Version="0.13.0" />
<PackageReference Include="Wolfgang.Etl.TestKit.Xunit" Version="0.13.0" />
<PackageReference Include="Wolfgang.Etl.TestKit" Version="0.14.0" />
<PackageReference Include="Wolfgang.Etl.TestKit.Xunit" Version="0.14.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" NoWarn="NU1701">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
179 changes: 179 additions & 0 deletions tests/Wolfgang.Etl.Xml.Tests.Unit/XmlCoverageTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.Extensions.Logging.Abstractions;
using Wolfgang.Etl.Xml.Tests.Unit.TestModels;
using Xunit;

namespace Wolfgang.Etl.Xml.Tests.Unit;

/// <summary>
/// Restores per-class coverage for paths the TestKit contract base stopped exercising after the
/// 0.14 bump — the typed-<c>ILogger&lt;T&gt;</c> constructors on the multi-stream extractor / both
/// loaders, the custom-<see cref="XmlWriterSettings"/> serialize branch, the invalid-NCName root
/// throw, and the deserialize-to-null skip.
/// </summary>
public sealed class XmlCoverageTests
{
private static readonly PersonRecord[] Sample =
{
new() { FirstName = "Alice", LastName = "Smith", Age = 30 },
};


[Fact]
public async Task MultiStreamExtractor_streams_logger_ctor_extracts()
{
var extractor = new XmlMultiStreamExtractor<PersonRecord>
(
SerializeEach(Sample),
NullLogger<XmlMultiStreamExtractor<PersonRecord>>.Instance
);

Assert.Equal(1, await CountAsync(extractor).ConfigureAwait(false));
}


[Fact]
public async Task MultiStreamExtractor_streams_readerSettings_logger_ctor_extracts()
{
var extractor = new XmlMultiStreamExtractor<PersonRecord>
(
SerializeEach(Sample),
new XmlReaderSettings(),
NullLogger<XmlMultiStreamExtractor<PersonRecord>>.Instance
);

Assert.Equal(1, await CountAsync(extractor).ConfigureAwait(false));
}


[Fact]
public async Task MultiStreamExtractor_when_a_stream_deserializes_to_null_skips_it()
{
const string nilXml =
"<PersonRecord xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:nil=\"true\" />";
var streams = new[] { new MemoryStream(Encoding.UTF8.GetBytes(nilXml)) };
var extractor = new XmlMultiStreamExtractor<PersonRecord>(streams);

Assert.Equal(0, await CountAsync(extractor).ConfigureAwait(false));
}


[Fact]
public async Task MultiStreamLoader_streamFactory_logger_ctor_loads()
{
var buffers = new List<MemoryStream>();
var loader = new XmlMultiStreamLoader<PersonRecord>
(
_ => Capture(buffers),
NullLogger<XmlMultiStreamLoader<PersonRecord>>.Instance
);

await loader.LoadAsync(ToAsync(Sample)).ConfigureAwait(false);

Assert.Equal(1, loader.CurrentItemCount);
}


[Fact]
public async Task MultiStreamLoader_streamFactory_writerSettings_logger_ctor_serializes_with_settings()
{
var buffers = new List<MemoryStream>();
var loader = new XmlMultiStreamLoader<PersonRecord>
(
_ => Capture(buffers),
new XmlWriterSettings { OmitXmlDeclaration = true },
NullLogger<XmlMultiStreamLoader<PersonRecord>>.Instance
);

await loader.LoadAsync(ToAsync(Sample)).ConfigureAwait(false);

Assert.Equal(1, loader.CurrentItemCount);
var xml = Encoding.UTF8.GetString(Assert.Single(buffers).ToArray());
// OmitXmlDeclaration = true suppresses the <?xml ?> prolog — proof the custom
// writer settings were actually applied, not silently discarded (the default
// serialize path emits the declaration).
Assert.DoesNotContain("<?xml", xml, StringComparison.Ordinal);
}


[Fact]
public async Task SingleStreamLoader_stream_logger_ctor_loads()
{
using var stream = new MemoryStream();
var loader = new XmlSingleStreamLoader<PersonRecord>
(
stream,
NullLogger<XmlSingleStreamLoader<PersonRecord>>.Instance
);

await loader.LoadAsync(ToAsync(Sample)).ConfigureAwait(false);

Assert.Equal(1, loader.CurrentItemCount);
}


[Fact]
public void SingleStreamLoader_when_rootElementName_is_not_a_valid_NCName_throws_ArgumentException()
{
var ex = Assert.Throws<ArgumentException>
(
() => new XmlSingleStreamLoader<PersonRecord>
(
new MemoryStream(),
new XmlSingleStreamLoaderOptions { RootElementName = "not a valid name" }
)
);

Assert.Equal("rootElementName", ex.ParamName);
}


private static MemoryStream Capture(List<MemoryStream> buffers)
{
var ms = new MemoryStream();
buffers.Add(ms);
return ms;
}


private static IEnumerable<Stream> SerializeEach(IEnumerable<PersonRecord> items)
{
var serializer = new XmlSerializer(typeof(PersonRecord));
foreach (var item in items)
{
var ms = new MemoryStream();
serializer.Serialize(ms, item);
ms.Position = 0;
yield return ms;
}
}


private static async Task<int> CountAsync(XmlMultiStreamExtractor<PersonRecord> extractor)
{
var count = 0;
await foreach (var _ in extractor.ExtractAsync().ConfigureAwait(false))
{
count++;
}

return count;
}


private static async IAsyncEnumerable<PersonRecord> ToAsync(IEnumerable<PersonRecord> items)
{
foreach (var item in items)
{
yield return item;
}

await Task.CompletedTask.ConfigureAwait(false);
}
}