feat: add FaultyExtractor/Loader/Transformer fault-injection doubles (#12)#173
Merged
Conversation
…12) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lyzer baseline (#12) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…es-12 # Conflicts: # src/Wolfgang.Etl.TestKit/PublicAPI.Unshipped.txt
This was referenced Jun 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds three fault-injection test doubles to
Wolfgang.Etl.TestKitfor exercising consumer error-handling paths (issue #12):FaultyExtractor<T>—ExtractorBase<T, Report>, yields from anIEnumerable<T>.FaultyLoader<T>—LoaderBase<T, Report>, optionalcollectItemsbuffer exposed viaGetCollectedItems().FaultyTransformer<T>—TransformerBase<T, T, Report>, pass-through.Fluent fault API (v1 scope)
Each double exposes the same composable, chainable API (every config method returns
this):ThrowAt(int index, Exception exception)— throws when reaching the zero-based post-skip index. The failing item is counted (IncrementCurrentItemCountruns) before the throw but is not yielded/loaded. Stored in aDictionary<int,Exception>(last-wins on duplicate index). Throw takes precedence over aDuplicateAtat the same index.ThrowAfterCompletion(Exception exception)— throws after all items are processed.DuplicateAt(int index)— emits/loads the item at that index twice consecutively; the duplicate is counted and respectsMaximumItemCount.Validation: negative
index→ArgumentOutOfRangeException("index"); nullexception→ArgumentNullException("exception").Deferred to a later iteration: delay injection, payload corruption, and probabilistic faults.
Progress / CurrentItemCount semantics on fault
For
ThrowAt(n, ...)with no skip,CurrentItemCount == n + 1at the point of throw — the failing item is counted so a progress report reflects the item that caused the failure.DuplicateAtincreases the total count by one per configured duplicate. Tests assert these exactly.Tests
49 new tests across
FaultyExtractorTests,FaultyLoaderTests,FaultyTransformerTests(T = int): no-fault pass-through,ThrowAt(exception identity, partial output, count, last-wins),ThrowAfterCompletion,DuplicateAt, composability (DuplicateAt(i)+ThrowAt(j), i<j), arg validation, fluent chaining, and timer-injection ctors. Suite: 58 → 107 tests, all green (-c Release -f net8.0). Source builds clean across all TFMs (net462/netstandard2.0/net481/net8.0/net10.0), 0 warnings.PublicAPI follow-up note
The PublicAPI analyzer baseline is not on
mainyet (open PR #166), so this PR does not touchPublicAPI.Unshipped.txt. After #166 merges, add the following entries tosrc/Wolfgang.Etl.TestKit/PublicAPI.Unshipped.txt:(The 3
protectedIProgressTimerconstructors are not public API and are intentionally omitted.)Closes #12
🤖 Generated with Claude Code