File tree Expand file tree Collapse file tree
tests/NeoReports.Core.UnitTests Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -58,6 +58,11 @@ The `NeoReports.Abstractions` contract follows SemVer strictly.
5858 the host — D20 — this is a nudge, not a behaviour change).
5959
6060### Fixed
61+ - ** Sectioned outputs are counted as outputs.** ` CompiledReport.OutputCount ` (and ` OutputFormats ` )
62+ omitted them, so a report with one plain and one sectioned output looked single-output: the API's
63+ sync mode — which supports single-output reports only — accepted it, the runner wrote both files,
64+ and the caller silently received whichever one directory enumeration yielded first. ` GET /reports `
65+ under-reported such a report's formats for the same reason.
6166- ** Google Sheets no longer drops a column whose header cell isn't text.** Requests ask for
6267 ` UNFORMATTED_VALUE ` , so a year-numbered header (` 2024 ` ) or a ` TRUE ` /` FALSE ` one arrives as a JSON
6368 number/boolean; the header index accepted strings only, while the data path decodes every kind. The
Original file line number Diff line number Diff line change @@ -258,7 +258,9 @@ rather than decided:
258258 error strings). The read- and write-failure paths in the same method scrub; the ** upload** path does
259259 not — and the sync endpoint deliberately suppresses the very same string, so one route hides what
260260 the other returns verbatim.
261- - ** API: sync mode's single-output guard ignores sectioned outputs.** ` OutputCount ` counts only
261+ - ~~ ** API: sync mode's single-output guard ignores sectioned outputs.** ~~ ** FIXED** — ` OutputCount `
262+ and ` OutputFormats ` now include sectioned outputs, so the guard rejects the mixed report it always
263+ claimed to and the listing reports every format. Original description: ` OutputCount ` counts only
262264 ` Outputs ` , so a report with one plain and one sectioned output passes the guard, the runner writes
263265 two artifacts, and the caller silently receives ** one** — which one decided by directory-enumeration
264266 order. The same undercount makes ` GET /reports ` under-report a sectioned report's formats.
Original file line number Diff line number Diff line change @@ -50,7 +50,13 @@ internal CompiledReport(
5050 RowCountFactory = countRows ;
5151 Deadline = deadline ;
5252
53- OutputFormats = outputs . Select ( o => o . Factory . Format ) . ToArray ( ) ;
53+ // Both collections produce a delivered artifact, so both belong in the count and the format
54+ // list. Counting only Outputs made a report with one plain and one sectioned output look
55+ // single-output: the API's sync guard let it through, the runner then wrote two files, and the
56+ // caller silently received whichever one directory enumeration happened to yield first.
57+ OutputFormats = outputs . Select ( o => o . Factory . Format )
58+ . Concat ( sectionedOutputs . Select ( s => s . Spec . Factory . Format ) )
59+ . ToArray ( ) ;
5460 DestinationTypes = destinations . Select ( d => d . Factory . Type ) . ToArray ( ) ;
5561 }
5662
@@ -64,7 +70,7 @@ internal CompiledReport(
6470 public int PageSize { get ; }
6571
6672 /// <summary>Number of configured outputs (formats).</summary>
67- public int OutputCount => Outputs . Count ;
73+ public int OutputCount => Outputs . Count + SectionedOutputs . Count ;
6874
6975 /// <summary>Format id of each configured output, in order (e.g. "csv", "xlsx").</summary>
7076 public IReadOnlyList < string > OutputFormats { get ; }
Original file line number Diff line number Diff line change @@ -97,6 +97,26 @@ public void A_view_without_columns_and_no_report_columns_is_rejected()
9797 Should . Throw < ConfigurationException > ( act ) . Message . ShouldContain ( "no columns" ) ;
9898 }
9999
100+ [ Fact ]
101+ public void Sectioned_outputs_are_counted_and_listed_alongside_plain_ones ( )
102+ {
103+ // Both kinds produce a delivered artifact. Counting only the plain ones made a report like
104+ // this one look single-output, so the API's sync guard — which promises "single-output reports
105+ // only" — let it through; the runner then wrote two files and the caller silently received
106+ // whichever one directory enumeration yielded first.
107+ var report = new ReportBuilder < Sale > ( "mixed" )
108+ . From ( new FakeBatchSource < Sale > ( new [ ] { Page ( 1 ) } ) )
109+ . Column ( v => v . Id , "Id" )
110+ . To ( new OutputSpec ( new FakeWriterFactory ( ) ) )
111+ . ToSections ( new SectionedOutputSpec ( new FakeSectionedWriterFactory ( ) ) , s => s
112+ . Section ( "All" , v => v . Where ( x => x . Id > 0 ) ) )
113+ . Build ( ) ;
114+
115+ report . OutputCount . ShouldBe ( 2 ) ;
116+ report . OutputFormats . ShouldContain ( "fake" ) ;
117+ report . OutputFormats . Count . ShouldBe ( 2 ) ;
118+ }
119+
100120 [ Fact ]
101121 public async Task Sectioned_output_projects_each_section_from_a_single_read ( )
102122 {
You can’t perform that action at this time.
0 commit comments