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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ dotnet run --project benchmarks/NeoReports.Benchmarks -c Release

## Estratégia de testes (inclua em cada PR)

- **xUnit + NSubstitute.** Asserções com FluentAssertions.
- **xUnit + NSubstitute.** Asserções com Shouldly (MIT; FluentAssertions saiu na v8 por virar licença comercial — ver `NeoReports-Decisoes.md`).
- **Writers: golden-file tests.** Saída comparada byte-a-byte / linha-a-linha com arquivo de referência versionado.
- **SQL source: Testcontainers** (SQL Server/Postgres efêmero), não mock de banco.
- **Memória: BenchmarkDotNet com `MemoryDiagnoser`** num report de 1M linhas — provar alocação ~constante (critério de aceite do MVP).
Expand Down
12 changes: 6 additions & 6 deletions build/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@
<PackageVersion Include="Hangfire.SqlServer" Version="1.8.14" />

<!-- Tests -->
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageVersion Include="FluentAssertions" Version="7.0.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
<PackageVersion Include="Shouldly" Version="4.2.1" />
<PackageVersion Include="NSubstitute" Version="5.3.0" />
<PackageVersion Include="Testcontainers.MsSql" Version="4.0.0" />
<PackageVersion Include="Xunit.SkippableFact" Version="1.5.23" />
<PackageVersion Include="Testcontainers.MsSql" Version="4.12.0" />
<PackageVersion Include="Xunit.SkippableFact" Version="1.5.61" />

<!-- Benchmarks -->
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Shouldly" />
<PackageReference Include="NSubstitute" />
</ItemGroup>

Expand Down
47 changes: 24 additions & 23 deletions tests/NeoReports.Core.UnitTests/PipelineTests.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System.Text;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using NeoReports.Abstractions;
using NeoReports.Core.Building;
using NeoReports.Core.Pipeline;
using NeoReports.Core.UnitTests.Fakes;
using Shouldly;
using Xunit;

namespace NeoReports.Core.UnitTests;
Expand Down Expand Up @@ -36,15 +36,16 @@ public async Task Reads_all_pages_writes_rows_and_uploads_file()

var result = await Run(report);

result.Status.Should().Be(ReportRunStatus.Completed);
result.Stats.RecordsRead.Should().Be(3);
result.Stats.RecordsWritten.Should().Be(3);
result.Stats.BatchesProcessed.Should().Be(2);
result.Uploads.Should().ContainSingle().Which.Success.Should().BeTrue();
result.Status.ShouldBe(ReportRunStatus.Completed);
result.Stats.RecordsRead.ShouldBe(3);
result.Stats.RecordsWritten.ShouldBe(3);
result.Stats.BatchesProcessed.ShouldBe(2);
result.Uploads.ShouldHaveSingleItem().Success.ShouldBeTrue();

destination.LastDestination!.Files.Should().ContainKey("r.fake");
destination.LastDestination!.Files.ShouldContainKey("r.fake");
var content = Encoding.UTF8.GetString(destination.LastDestination.Files["r.fake"]);
content.Should().Contain("1").And.Contain("3");
content.ShouldContain("1");
content.ShouldContain("3");
}

[Fact]
Expand All @@ -63,11 +64,11 @@ public async Task Multi_output_reads_source_only_once_per_page()

var result = await Run(report);

result.Status.Should().Be(ReportRunStatus.Completed);
source.ReadCalls.Should().Be(2);
csvLike.LastWriter!.Rows.Should().HaveCount(4);
xlsxLike.LastWriter!.Rows.Should().HaveCount(4);
csvLike.LastWriter!.Finalized.Should().BeTrue();
result.Status.ShouldBe(ReportRunStatus.Completed);
source.ReadCalls.ShouldBe(2);
csvLike.LastWriter!.Rows.Count.ShouldBe(4);
xlsxLike.LastWriter!.Rows.Count.ShouldBe(4);
csvLike.LastWriter!.Finalized.ShouldBeTrue();
}

[Fact]
Expand All @@ -85,9 +86,9 @@ public async Task Filter_excludes_non_matching_rows()

var result = await Run(report);

result.Stats.RecordsRead.Should().Be(4);
result.Stats.RecordsWritten.Should().Be(2);
writer.LastWriter!.Rows.Select(r => (long)r[0]!).Should().Equal(2, 4);
result.Stats.RecordsRead.ShouldBe(4);
result.Stats.RecordsWritten.ShouldBe(2);
writer.LastWriter!.Rows.Select(r => (long)r[0]!).ShouldBe(new long[] { 2, 4 });
}

[Fact]
Expand All @@ -106,10 +107,10 @@ public async Task Streaming_source_is_sliced_into_batches()

var result = await Run(report);

result.Status.Should().Be(ReportRunStatus.Completed);
result.Stats.RecordsRead.Should().Be(5);
result.Stats.BatchesProcessed.Should().Be(3);
writer.LastWriter!.Rows.Should().HaveCount(5);
result.Status.ShouldBe(ReportRunStatus.Completed);
result.Stats.RecordsRead.ShouldBe(5);
result.Stats.BatchesProcessed.ShouldBe(3);
writer.LastWriter!.Rows.Count.ShouldBe(5);
}

[Fact]
Expand All @@ -130,8 +131,8 @@ public async Task Mapping_from_source_type_projects_to_row_type()

var result = await Run(report);

result.Status.Should().Be(ReportRunStatus.Completed);
writer.LastWriter!.Rows.Should().HaveCount(2);
writer.LastWriter!.Rows.Select(r => (string)r[1]!).Should().Equal("a", "b");
result.Status.ShouldBe(ReportRunStatus.Completed);
writer.LastWriter!.Rows.Count.ShouldBe(2);
writer.LastWriter!.Rows.Select(r => (string)r[1]!).ShouldBe(new[] { "a", "b" });
}
}
27 changes: 13 additions & 14 deletions tests/NeoReports.Core.UnitTests/RegistrationTests.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using NeoReports.Abstractions;
using NeoReports.Core.Building;
using NeoReports.Core.DependencyInjection;
using NeoReports.Core.Pipeline;
using NeoReports.Core.Registry;
using NeoReports.Core.UnitTests.Fakes;
using Shouldly;
using Xunit;

namespace NeoReports.Core.UnitTests;
Expand Down Expand Up @@ -35,18 +35,18 @@ public void AddReport_registers_typed_report_and_schema()
var provider = services.BuildServiceProvider();
var registry = provider.GetRequiredService<IReportRegistry>();

registry.Contains("vendas-mensal").Should().BeTrue();
registry.Names.Should().ContainSingle().Which.Should().Be("vendas-mensal");
registry.Contains("vendas-mensal").ShouldBeTrue();
registry.Names.ShouldHaveSingleItem().ShouldBe("vendas-mensal");

var report = registry.Find("vendas-mensal");
report.Should().NotBeNull();
report!.Schema.Columns.Select(c => c.Name).Should().Equal("Id", "Cliente", "Valor", "Data");
report.Schema.Find("Valor")!.Type.Should().Be(ColumnType.Decimal);
report.Schema.Find("Id")!.Type.Should().Be(ColumnType.Integer);
report.Schema.Find("Valor")!.DisplayName.Should().Be("Valor");
report.OutputCount.Should().Be(1);
report.ShouldNotBeNull();
report.Schema.Columns.Select(c => c.Name).ShouldBe(new[] { "Id", "Cliente", "Valor", "Data" });
report.Schema.Find("Valor")!.Type.ShouldBe(ColumnType.Decimal);
report.Schema.Find("Id")!.Type.ShouldBe(ColumnType.Integer);
report.Schema.Find("Valor")!.DisplayName.ShouldBe("Valor");
report.OutputCount.ShouldBe(1);

provider.GetRequiredService<IReportRunner>().Should().BeOfType<ReportRunner>();
provider.GetRequiredService<IReportRunner>().ShouldBeOfType<ReportRunner>();
}

[Fact]
Expand All @@ -62,8 +62,7 @@ void Register() => services.AddReport<Venda>("dup", b => b
.To(new OutputSpec(new FakeWriterFactory())));

Register();
var act = Register;
act.Should().Throw<ConfigurationException>();
Should.Throw<ConfigurationException>(Register);
}

[Fact]
Expand All @@ -73,7 +72,7 @@ public void Build_without_source_throws()
.Column(v => v.Id, "Id")
.Build();

act.Should().Throw<ConfigurationException>().WithMessage("*no source*");
Should.Throw<ConfigurationException>(act).Message.ShouldContain("no source");
}

[Fact]
Expand All @@ -82,6 +81,6 @@ public void Build_without_columns_throws()
var source = new FakeBatchSource<Venda>(new[] { new[] { new Venda(1, "A", 1m, DateTime.UnixEpoch) } });
var act = () => new ReportBuilder<Venda>("x").From(source).Build();

act.Should().Throw<ConfigurationException>().WithMessage("*no columns*");
Should.Throw<ConfigurationException>(act).Message.ShouldContain("no columns");
}
}
25 changes: 13 additions & 12 deletions tests/NeoReports.Core.UnitTests/ResilienceTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using NeoReports.Abstractions;
using NeoReports.Core.Building;
using NeoReports.Core.Pipeline;
using NeoReports.Core.UnitTests.Fakes;
using Shouldly;
using Xunit;

namespace NeoReports.Core.UnitTests;
Expand Down Expand Up @@ -46,9 +46,9 @@ public async Task Transient_read_failure_is_retried_and_report_completes()
var report = Build(source, writer, b => b.Retry(r => r.MaxAttempts(3).Constant(TimeSpan.Zero)));
var result = await Run(report);

result.Status.Should().Be(ReportRunStatus.Completed);
result.Stats.Retries.Should().Be(2);
writer.LastWriter!.Rows.Should().HaveCount(4);
result.Status.ShouldBe(ReportRunStatus.Completed);
result.Stats.Retries.ShouldBe(2);
writer.LastWriter!.Rows.Count.ShouldBe(4);
}

[Fact]
Expand All @@ -60,8 +60,8 @@ public async Task Abort_strategy_fails_report_on_definitive_failure()
var report = Build(source, writer, b => b.OnFailure(f => f.AbortReport()));
var result = await Run(report);

result.Status.Should().Be(ReportRunStatus.Failed);
result.Error.Should().NotBeNullOrEmpty();
result.Status.ShouldBe(ReportRunStatus.Failed);
result.Error.ShouldNotBeNullOrEmpty();
}

[Fact]
Expand All @@ -73,9 +73,9 @@ public async Task Skip_strategy_skips_failed_batch_and_marks_partial()
var report = Build(source, writer, b => b.OnFailure(f => f.SkipBatchAndLog()));
var result = await Run(report);

result.Status.Should().Be(ReportRunStatus.CompletedPartial);
result.SkippedBatches.Should().Be(1);
writer.LastWriter!.Rows.Select(r => (long)r[0]!).Should().Equal(1, 3);
result.Status.ShouldBe(ReportRunStatus.CompletedPartial);
result.SkippedBatches.ShouldBe(1);
writer.LastWriter!.Rows.Select(r => (long)r[0]!).ShouldBe(new long[] { 1, 3 });
}

[Fact]
Expand All @@ -88,8 +88,9 @@ public async Task Threshold_aborts_even_in_skip_mode()
.OnFailure(f => f.SkipBatchAndLog().AbortIf(t => t.ConsecutiveFailures(3))));
var result = await Run(report);

result.Status.Should().Be(ReportRunStatus.Failed);
result.SkippedBatches.Should().Be(2);
result.Error.Should().Contain("threshold");
result.Status.ShouldBe(ReportRunStatus.Failed);
result.SkippedBatches.ShouldBe(2);
result.Error.ShouldNotBeNull();
result.Error.ShouldContain("threshold");
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Text;
using FluentAssertions;
using NeoReports.Abstractions;
using NeoReports.Destinations.Local;
using Shouldly;
using Xunit;

namespace NeoReports.Destinations.Local.UnitTests;
Expand All @@ -28,10 +28,11 @@ public async Task Writes_file_to_resolved_path()

var result = await destination.UploadAsync(FileOf("vendas.csv", "a,b\n1,2\n"), Context(), CancellationToken.None);

result.Success.Should().BeTrue();
File.Exists(result.RemotePath).Should().BeTrue();
(await File.ReadAllTextAsync(result.RemotePath!)).Should().Be("a,b\n1,2\n");
Path.GetFileName(result.RemotePath!).Should().StartWith("vendas-").And.EndWith(".csv");
result.Success.ShouldBeTrue();
File.Exists(result.RemotePath).ShouldBeTrue();
(await File.ReadAllTextAsync(result.RemotePath!)).ShouldBe("a,b\n1,2\n");
Path.GetFileName(result.RemotePath!).ShouldStartWith("vendas-");
Path.GetFileName(result.RemotePath!).ShouldEndWith(".csv");
}

[Fact]
Expand All @@ -43,9 +44,9 @@ public async Task Overwrites_existing_file_atomically()
await destination.UploadAsync(FileOf("r.csv", "old"), Context(), CancellationToken.None);
var result = await destination.UploadAsync(FileOf("r.csv", "new"), Context(), CancellationToken.None);

(await File.ReadAllTextAsync(result.RemotePath!)).Should().Be("new");
(await File.ReadAllTextAsync(result.RemotePath!)).ShouldBe("new");
// No leftover temp files in the directory.
Directory.GetFiles(_root).Should().ContainSingle();
Directory.GetFiles(_root).ShouldHaveSingleItem();
}

public void Dispose()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Shouldly" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using FluentAssertions;
using NeoReports.Destinations.Local;
using Shouldly;
using Xunit;

namespace NeoReports.Destinations.Local.UnitTests;
Expand All @@ -12,28 +12,28 @@ public class PathTemplateTests
public void Expands_name_ext_and_default_date()
{
var result = PathTemplate.Expand("{name}-{date}.{ext}", "vendas", "csv", Ts);
result.Should().Be("vendas-2026-03-07.csv");
result.ShouldBe("vendas-2026-03-07.csv");
}

[Fact]
public void Expands_date_with_custom_format()
{
var result = PathTemplate.Expand("out/{name}_{date:yyyyMM}.{ext}", "rel", "xlsx", Ts);
result.Should().Be("out/rel_202603.xlsx");
result.ShouldBe("out/rel_202603.xlsx");
}

[Fact]
public void Expands_parameter_tokens()
{
var parameters = new Dictionary<string, object?> { ["regiao"] = "sul" };
var result = PathTemplate.Expand("{name}-{regiao}.{ext}", "rel", "csv", Ts, parameters);
result.Should().Be("rel-sul.csv");
result.ShouldBe("rel-sul.csv");
}

[Fact]
public void Leaves_unknown_tokens_untouched()
{
var result = PathTemplate.Expand("{name}-{missing}.{ext}", "rel", "csv", Ts);
result.Should().Be("rel-{missing}.csv");
result.ShouldBe("rel-{missing}.csv");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Shouldly" />
<PackageReference Include="NSubstitute" />
<PackageReference Include="AWSSDK.S3" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
Expand Down
Loading