Skip to content

feat: add FaultyExtractor/Loader/Transformer fault-injection doubles (#12)#173

Merged
Chris-Wolfgang merged 4 commits into
vNextfrom
feature/faulty-doubles-12
Jun 24, 2026
Merged

feat: add FaultyExtractor/Loader/Transformer fault-injection doubles (#12)#173
Chris-Wolfgang merged 4 commits into
vNextfrom
feature/faulty-doubles-12

Conversation

@Chris-Wolfgang

Copy link
Copy Markdown
Owner

Summary

Adds three fault-injection test doubles to Wolfgang.Etl.TestKit for exercising consumer error-handling paths (issue #12):

  • FaultyExtractor<T>ExtractorBase<T, Report>, yields from an IEnumerable<T>.
  • FaultyLoader<T>LoaderBase<T, Report>, optional collectItems buffer exposed via GetCollectedItems().
  • 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 (IncrementCurrentItemCount runs) before the throw but is not yielded/loaded. Stored in a Dictionary<int,Exception> (last-wins on duplicate index). Throw takes precedence over a DuplicateAt at 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 respects MaximumItemCount.

Validation: negative indexArgumentOutOfRangeException("index"); null exceptionArgumentNullException("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 + 1 at the point of throw — the failing item is counted so a progress report reflects the item that caused the failure. DuplicateAt increases 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 main yet (open PR #166), so this PR does not touch PublicAPI.Unshipped.txt. After #166 merges, add the following entries to src/Wolfgang.Etl.TestKit/PublicAPI.Unshipped.txt:

Wolfgang.Etl.TestKit.FaultyExtractor<T>
Wolfgang.Etl.TestKit.FaultyExtractor<T>.FaultyExtractor(System.Collections.Generic.IEnumerable<T!>! items) -> void
Wolfgang.Etl.TestKit.FaultyExtractor<T>.ThrowAt(int index, System.Exception! exception) -> Wolfgang.Etl.TestKit.FaultyExtractor<T!>!
Wolfgang.Etl.TestKit.FaultyExtractor<T>.ThrowAfterCompletion(System.Exception! exception) -> Wolfgang.Etl.TestKit.FaultyExtractor<T!>!
Wolfgang.Etl.TestKit.FaultyExtractor<T>.DuplicateAt(int index) -> Wolfgang.Etl.TestKit.FaultyExtractor<T!>!
Wolfgang.Etl.TestKit.FaultyLoader<T>
Wolfgang.Etl.TestKit.FaultyLoader<T>.FaultyLoader(bool collectItems) -> void
Wolfgang.Etl.TestKit.FaultyLoader<T>.GetCollectedItems() -> System.Collections.Generic.IReadOnlyList<T!>?
Wolfgang.Etl.TestKit.FaultyLoader<T>.ThrowAt(int index, System.Exception! exception) -> Wolfgang.Etl.TestKit.FaultyLoader<T!>!
Wolfgang.Etl.TestKit.FaultyLoader<T>.ThrowAfterCompletion(System.Exception! exception) -> Wolfgang.Etl.TestKit.FaultyLoader<T!>!
Wolfgang.Etl.TestKit.FaultyLoader<T>.DuplicateAt(int index) -> Wolfgang.Etl.TestKit.FaultyLoader<T!>!
Wolfgang.Etl.TestKit.FaultyTransformer<T>
Wolfgang.Etl.TestKit.FaultyTransformer<T>.FaultyTransformer() -> void
Wolfgang.Etl.TestKit.FaultyTransformer<T>.ThrowAt(int index, System.Exception! exception) -> Wolfgang.Etl.TestKit.FaultyTransformer<T!>!
Wolfgang.Etl.TestKit.FaultyTransformer<T>.ThrowAfterCompletion(System.Exception! exception) -> Wolfgang.Etl.TestKit.FaultyTransformer<T!>!
Wolfgang.Etl.TestKit.FaultyTransformer<T>.DuplicateAt(int index) -> Wolfgang.Etl.TestKit.FaultyTransformer<T!>!

(The 3 protected IProgressTimer constructors are not public API and are intentionally omitted.)

Closes #12

🤖 Generated with Claude Code

…12)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Chris-Wolfgang Chris-Wolfgang changed the base branch from main to vNext June 23, 2026 23:32
Chris-Wolfgang and others added 3 commits June 23, 2026 19:38
…lyzer baseline (#12)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…es-12

# Conflicts:
#	src/Wolfgang.Etl.TestKit/PublicAPI.Unshipped.txt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhancement: Chaos/Fault Injection Test Doubles

1 participant